浅谈并实现自制加密算法

为什么是“浅谈”呢?因为我对加密算法的了解程度不是很深入,只能浅谈,哈哈哈。

本文自制的加密算法逻辑主要基于“古典密码学”设计,属于“对称性加密”。

前言:我把解决、设计思路、解决过程都记录下来了,有什么需要改进的地方,敬请不吝赐教。

场景:

在迎新管理系统的“电子注册单”移动端页面上面需要显示以“考生号”作为内容的条形码,考虑到安全问题,需要将“考生号”进行加密后再作为条形码的内容。

但是往往经过加密后的密文长度会大于明文长度,因为普遍的加密算法会进行“填充”与“加盐”操作,虽说可以选择不“加盐”,但是如果需要不进行“填充”操作,则需要明文的长度是分组的倍数,对明文长度的束缚很大。而且考生号(明文)的长度已经很长了,如果将密文作为条形码的内容在有限的移动端页面宽度下,条形码的条纹会很细很拥挤,从而导致无法正常扫描识别此条形码的内容。

所以有两种解决方案:

1、点击“条形码”进入全屏显示“条形码”模式。

2、只制加密算法,让密文长度至少与明文长度一致。这里采用第二种方案。

解决思路:

1、按我个人的理解先简单介绍一下摘要算法、加密算法和编码(表述不一定准确噢)

摘要算法:MD5SHA1这些都是摘要算法,而不是加密算法噢!望文生义,摘要就是根据对应算法对原文进行摘要,所以的到的都是固定长度的“摘要值“。会存在不同明文内容但产生同样的“摘要值“噢,这个涉及到了算法的”抗碰撞性“,MD5现在已经不安全了,SHA256摘要算法对目前来说”抗碰撞性“还是不错的。摘要算法主要应用于计算数据或者文件“指纹”。

编码:Base64Url编码这些就是编码,编码是希望所谓“密文”能够被“解密”的,编码的目标是数据传输、数据存储和数据压缩/解压缩。

加密:一般分为“对称性加密”与“非对称性加密”。“对称性加密”指的是加解密使用的秘钥是一样的,如AES加密;而“非对称性加密”这是指加解密使用的秘钥是不同的,如RSA加密。即是平常说的公钥和私钥。加密是可以算是一种编码的操作,但是编码并不是加密噢。

2、如何自制加密算法呢?

记得在初中刚接触编程的时候,也有写过类似的自制加密算法。下面围绕着“对称性加密”与“古典加密算法”进行加密逻辑的设计。

加密逻辑大概如下:

比如有五张扑克牌:2、3、J、Q、K。这五张牌相当于“明文”

①将牌分为两叠:

像洗牌一样,将扑克牌平分为两叠(可以自定义其他叠数/分组数),左右手各拿一叠,若牌数为奇数则右手比左手多一张。

左手:2,3;右手:J、Q、K

②洗牌:

右手牌多,右手先放一张,然后左手再放一张,按此循环往复。最终会合成一叠牌,顺序为:J、2、Q、3、K。这种就是古典加密算法里面的“栅栏加密”。

③凯撒(字典)加密:

比如我打算将现在扑克牌的顺序设置为我电脑的密码,但是怕忘记这个密码,所以需要将这个密码用只有我自己知道的“暗号”来记录下来。

比如五张牌的顺序点数(2、3、J、Q、K)分别对应"太阳系中由近及远的8颗行星"(水星、金星、地星、火星、木星...)

扑克牌点数行星名称
2水星
3金星
J地星
Q火星
K木星

则现在扑克牌的顺序(J、2、Q、3、K)用“暗号”来记录(行星名称简写了)就是:地、水、火、金、木,这样就算比如看到这串“密文”也不知道是原来(J、2、Q、3、K)的意思。

这种是古典加密算法中的“恺撒加密算法”,本质就是将文本按照“字典”(就是上面所说的“暗号”)的映射关系(也就是上面的表格内容)进行替换,所以也叫“字典加密”、“替换加密”。此处"太阳系中由近及远的8颗行星"(水星、金星、地星、火星、木星...)就相当于是加解密的秘钥

④再次乱序:

觉得好像“暗号”(密文)还是能看出一点规律,再次打乱一下顺序,像第一步一样将“暗号”(密文)分为两叠,将上下两叠互换位置。顺序为:火、金、木、地、水。这样就可以把“暗号”(火、金、木、地、水)写在其他地方了,就算比如找到也不知道其对应的意思。

如何解密呢?

按照前面加密的步骤反过来操作即可解密获得明文。

加密代码实现:

1、进行“栅栏加密”

即为上述的①②(将牌分为两叠、洗牌)

    /**
     * Description: 进行栅栏解密
     * @param: [plaintext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String disorderOrder(String plaintext) {
        int preTextLength = plaintext.length() / 2;
        String prefixText = plaintext.substring(0, preTextLength);
        String suffixText = plaintext.substring(preTextLength);
        char[] prefixTextChars = prefixText.toCharArray();
        char[] suffixTextChars = suffixText.toCharArray();
        StringBuilder encryptResultBuilder = new StringBuilder();
        for (int i = 0; i < suffixTextChars.length; i++) {
            encryptResultBuilder.append(suffixTextChars[i]);
            if (preTextLength > i) {
                encryptResultBuilder.append(prefixTextChars[i]);
            }
        }
        return encryptResultBuilder.toString();
    }

2、进行“凯撒加密”

即为上述的③凯撒(字典)加密。

initOrder相当于“2、3、J、Q、K”

disorder相当于“水星、金星、地星、火星、木星”

也就是明文与密文对应关系为:1→3、2→p、3→k ....

    /**初始字典顺序*/
    private static String initOrder = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /**乱序后的字典顺序,相当于加解密的密钥*/
    private static String disorder = "TqfINOtoicB0v3pk6Z8JWlw2CxDUmzQLEd9nbr5SjeHVAs1FMgGXRP7ya4Yh";

    /**
     * Description: 进行字典(替换)加密。根据初始的字典顺序与乱序后的字典顺序的映射关系,对明文字符进行替换
     * @param: [plaintext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String enReplace(String plaintext) {
        StringBuilder enReplaceResultBuilder = new StringBuilder();
        for (char c : plaintext.toCharArray()) {
            enReplaceResultBuilder.append(disorder.substring(initOrder.indexOf(c), initOrder.indexOf(c) + 1));
        }
        return enReplaceResultBuilder.toString();
    }

3、再次乱序

对于上述的步骤④再次乱序

//encryptResult为前面加密的结果
int preTextLength = (encryptResult.length()) / 2;
String prefixText = encryptResult.substring(0, preTextLength);
String suffixText = encryptResult.substring(preTextLength);
return suffixText + prefixText;

自制加密算法加、解密工具类(NNPEncryptUtil)

现只对支持由数字、大小写字母组成的明文/密文进行加解密,如果需要对其他字符进行加解密在初始字典与乱序字典中添加即可

加密方法:encrypt(String plaintext);

解密方法:decrypt(String ciphertext);

/**
 * Description: NNP加解密工具类
 *
 * @Author: NonNullPointer
 */
public class NNPEncryptUtil {
    /**初始字典顺序*/
    private static String initOrder = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /**乱序后的字典顺序,相当于加解密的密钥*/
    private static String disorder = "TqfINOtoicB0v3pk6Z8JWlw2CxDUmzQLEd9nbr5SjeHVAs1FMgGXRP7ya4Yh";

    /**
     * Description: 进行字典(替换)加密。根据初始的字典顺序与乱序后的字典顺序的映射关系,对明文字符进行替换
     * @param: [plaintext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String enReplace(String plaintext) {
        StringBuilder enReplaceResultBuilder = new StringBuilder();
        for (char c : plaintext.toCharArray()) {
            enReplaceResultBuilder.append(disorder.substring(initOrder.indexOf(c), initOrder.indexOf(c) + 1));
        }
        return enReplaceResultBuilder.toString();
    }

    /**
     * Description: 进行字典(替换)解密。根据初始的字典顺序与乱序后的字典顺序的映射关系,对密文字符进行替换
     * @param: [ciphertext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String deReplace(String ciphertext) {
        StringBuilder deReplaceResultBuilder = new StringBuilder();
        for (char c : ciphertext.toCharArray()) {
            deReplaceResultBuilder.append(initOrder.substring(disorder.indexOf(c), disorder.indexOf(c) + 1));
        }
        return deReplaceResultBuilder.toString();
    }


    /**
     * Description: 进行栅栏解密
     * @param: [plaintext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String disorderOrder(String plaintext) {
        int preTextLength = plaintext.length() / 2;
        String prefixText = plaintext.substring(0, preTextLength);
        String suffixText = plaintext.substring(preTextLength);
        char[] prefixTextChars = prefixText.toCharArray();
        char[] suffixTextChars = suffixText.toCharArray();
        StringBuilder encryptResultBuilder = new StringBuilder();
        for (int i = 0; i < suffixTextChars.length; i++) {
            encryptResultBuilder.append(suffixTextChars[i]);
            if (preTextLength > i) {
                encryptResultBuilder.append(prefixTextChars[i]);
            }
        }
        return encryptResultBuilder.toString();
    }

    /**
     * Description: 进行栅栏解密
     * @param: [ciphertext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String restoreOrder(String ciphertext) {
        char[] ciphertextAsCharArr = ciphertext.toCharArray();
        StringBuilder prefixTextBuilder = new StringBuilder();
        StringBuilder suffixTextBuilder = new StringBuilder();
        for (int i = 0; i < ciphertextAsCharArr.length; i++) {
            if (i % 2 == 0) {
                suffixTextBuilder.append(ciphertextAsCharArr[i]);
            } else {
                prefixTextBuilder.append(ciphertextAsCharArr[i]);
            }
        }
        return prefixTextBuilder.toString() + suffixTextBuilder.toString();
    }


    /**
     * Description: 加密。对明文进行加密
     * @param: [plaintext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String encrypt(String plaintext) {
        String encryptResult = enReplace(disorderOrder(plaintext));
        int preTextLength = (encryptResult.length()) / 2;
        String prefixText = encryptResult.substring(0, preTextLength);
        String suffixText = encryptResult.substring(preTextLength);
        return suffixText + prefixText;
    }

    /**
     * Description: 解密。对密文进行加密
     * @param: [ciphertext]
     * @Author: NonNullPointer
     * @return: java.lang.String
     */
    public static String decrypt(String ciphertext) {
        int suffixTextLength = ciphertext.length() - (ciphertext.length() / 2);
        String suffixText = ciphertext.substring(0, suffixTextLength);
        String prefixText = ciphertext.substring(suffixTextLength);
        return deReplace(restoreOrder(prefixText + suffixText));
    }
}

文章不足之处还请斧正!

本文By:NonNullPointer --2023/12/08

最后修改:2023 年 12 月 08 日
如果觉得我的文章对你有用,请随意赞赏