浅谈并实现自制加密算法
为什么是“浅谈”呢?因为我对加密算法的了解程度不是很深入,只能浅谈,哈哈哈。
本文自制的加密算法逻辑主要基于“古典密码学”设计,属于“对称性加密”。
前言:我把解决、设计思路、解决过程都记录下来了,有什么需要改进的地方,敬请不吝赐教。
场景:
在迎新管理系统的“电子注册单”移动端页面上面需要显示以“考生号”作为内容的条形码,考虑到安全问题,需要将“考生号”进行加密后再作为条形码的内容。
但是往往经过加密后的密文长度会大于明文长度,因为普遍的加密算法会进行“填充”与“加盐”操作,虽说可以选择不“加盐”,但是如果需要不进行“填充”操作,则需要明文的长度是分组的倍数,对明文长度的束缚很大。而且考生号(明文)的长度已经很长了,如果将密文作为条形码的内容在有限的移动端页面宽度下,条形码的条纹会很细很拥挤,从而导致无法正常扫描识别此条形码的内容。
所以有两种解决方案:
1、点击“条形码”进入全屏显示“条形码”模式。
2、只制加密算法,让密文长度至少与明文长度一致。这里采用第二种方案。
解决思路:
1、按我个人的理解先简单介绍一下摘要算法、加密算法和编码(表述不一定准确噢)
摘要算法:MD5
、SHA1
这些都是摘要算法,而不是加密算法噢!望文生义,摘要就是根据对应算法对原文进行摘要,所以的到的都是固定长度的“摘要值“。会存在不同明文内容但产生同样的“摘要值“噢,这个涉及到了算法的”抗碰撞性“,MD5现在已经不安全了,SHA256摘要算法对目前来说”抗碰撞性“还是不错的。摘要算法主要应用于计算数据或者文件“指纹”。
编码:Base64
、Url
编码这些就是编码,编码是希望所谓“密文”能够被“解密”的,编码的目标是数据传输、数据存储和数据压缩/解压缩。
加密:一般分为“对称性加密”与“非对称性加密”。“对称性加密”指的是加解密使用的秘钥是一样的,如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