博学,切问,近思--詹子知 (https://jameszhan.github.io)
在密码学中,恺撒密码(或称恺撒加密、恺撒变换、变换加密)是一种最简单且最广为人知的加密技术。它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。
恺撒密码的加密、解密方法还能够通过同余的数学方法进行计算。首先将字母用数字代替,A=0,B=1,...,Z=25。此时偏移量为n的加密方法即为: E(x) = (x + n) mod 26.
解密就是:
D(x) = (x - n) mod 26.
显而易见,一旦确定了某两个字母的对应关系(即n的值),这种移位密码很容易被破解。
因此,为了使密码有更高的安全性,单字母替换密码就出现了。
明码表:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
密码表:T U V W X Y Z A B C D E F G H I J K L M N O P Q R S
但是这种加密方式依然可以破解,根据字母使用频度表,分析密文中的字母频率,将其对照即可破解。
不仅如此,凯撒加密对加密数据也是有要求的,一般情况下,它只支持对基本的英文字母进行加密,如果对中文等亚太地区的文字进行加密,结果可想而知,你的隐私将毫无保留的出现在众人面前。有人说,我们可以扩展这个算法,使它支持所有的文字,这么做是可行的,如果采用同余式的方式实现,代码几乎不怎么需要改动,只要字符集本身是Unicode就可以了。但是这种加密的安全性很难满足应用的要求。如果采用单字母替换的方式,程序将需要构建两个巨大的字符数组去保存他们的映射关系,而且扩展性也不好,当然也是不可行的。这样看来,凯撒加密岂不是一无是处了,其实对于一般的应用,凯撒加密还是足以应付的,只要我们对它稍作改进。
那么有什么样改进的方法呢?试想一下,如果被加密的数据本身就杂乱无章,毫无意义,那么人们就无法利用字母频度表来对数据进行破解攻击。如果有个工具能把我们需要加密的数据转换成简单的字符并杂乱无章,这样不就可以解决我们前面提到的问题了吗。看过前文的朋友一定还记得,有个叫Base64的字符编码工具,它刚好可以胜任这个工作。
到这里,大家一定知道我们这种加密方法的思路了。分两步走:
- 利用Base64对需要加密的字符串进行编码,生成毫无意义的Base64 String。
- 利用凯撒加密方式对Base64 String进行加密。
这时我们已经得到了加密后的字符串了,这个密文已经很难被破解了,是不是很Cool啊,这种方式已经足以满足大部分应用的安全需求。有人可能会问,Base64本身不是可以达到一种加密效果吗,为什么还要对他进行再次加密呢。这个问题的原因在于,Base64算法本身是公开的,拿到Base64 String后,它只要使用Base64对其进行解码就可以看到你的信息。而在这里,凯撒加密算法是我们自己的定制的,这个映射关系一定需要保密,否则我们的算法将起不到任何的加密效果。
public class CaesarCipher
{
private static final char[] UC_ENCRYPT_CHARS = { 'M', 'D', 'X', 'U', 'P', 'I', 'B', 'E', 'J', 'C', 'T', 'N',
'K', 'O', 'G', 'W', 'R', 'S', 'F', 'Y', 'V', 'L', 'Z', 'Q', 'A', 'H' };
private static final char[] LC_ENCRYPT_CHARS = { 'm', 'd', 'x', 'u', 'p', 'i', 'b', 'e', 'j', 'c', 't', 'n',
'k', 'o', 'g', 'w', 'r', 's', 'f', 'y', 'v', 'l', 'z', 'q', 'a', 'h' };
private static char[] UC_DECRYPT_CHARS = new char[26];
private static char[] LC_DECRYPT_CHARS = new char[26];
static {
for (int i = 0; i < 26; i++) {
char b = UC_ENCRYPT_CHARS[i];
UC_DECRYPT_CHARS[b - 'A'] = (char) ('A' + i);
b = LC_ENCRYPT_CHARS[i];
LC_DECRYPT_CHARS[b - 'a'] = (char) ('a' + i);
}
}
public static char encrypt(char b) {
if (b >= 'A' && b <= 'Z') {
return UC_ENCRYPT_CHARS[b - 'A'];
} else if (b >= 'a' && b <= 'z') {
return LC_ENCRYPT_CHARS[b - 'a'];
} else {
return b;
}
}
public static char decrypt(char b) {
if (b >= 'A' && b <= 'Z') {
return UC_DECRYPT_CHARS[b - 'A'];
} else if (b >= 'a' && b <= 'z') {
return LC_DECRYPT_CHARS[b - 'a'];
} else {
return b;
}
}
public static String encrypt(String input){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < input.length(); i++){
sb.append(encrypt(input.charAt(i)));
}
return sb.toString();
}
public static String decrypt(String input){
StringBuilder sb = new StringBuilder();
for(int i = 0; i < input.length(); i++){
sb.append(decrypt(input.charAt(i)));
}
return sb.toString();
}
}
关于Base64大家可以参照:Base64算法实现
我们看一个例子:
public static void main(String[] args) throws UnsupportedEncodingException
{
String plainText = "There is a tree!";
String base64String = Base64.encode(plainText.getBytes("utf-8"));
//base64 String = VGhlcmUgaXMgYSB0cmVlIQ==
System.out.println(CaesarCipher.encrypt(base64String));
}
这时可以的到加密后的字符串:LBenxkVbmQKbAFD0xkLnJR==
解密的过程与之刚好相反。
- 利用凯撒加密方式对加密后的数据进行解密,得到Base64 String。
- 利用Base64对Base64 String进行解码,得到最终的明文。
public static void main(String[] args) throws UnsupportedEncodingException
{
String cipherText = "LBenxkVbmQKbAFD0xkLnJR==";
String base64String = CaesarCipher.decrypt(cipherText);
//base64 String = VGhlcmUgaXMgYSB0cmVlIQ==
System.out.println(new String(Base64.decode(base64String), "utf-8"));
}
得到加密前的明文:There is a tree!
同样,我们也可以采用这种方式对多字节的字符串进行加密:
public static void main(String[] args) throws UnsupportedEncodingException
{
String plainText = "中华人民共和国万岁!";
String base64String = Base64.encode(plainText.getBytes("utf-8"));
//base64String = 5Lit5Y2O5Lq65rCR5YWx5ZKM5Zu95LiH5bKB77yB
System.out.println(CaesarCipher.encrypt(base64String));
}
得到密文:5Njy5A2G5Nr65sXS5AZq5HTK5Hv95NjE5dTD77aD
解密过程如下:
public static void main(String[] args) throws UnsupportedEncodingException
{
String cipherText = "5Njy5A2G5Nr65sXS5AZq5HTK5Hv95NjE5dTD77aD";
String base64String = CaesarCipher.decrypt(cipherText);
//base64String = 5Lit5Y2O5Lq65rCR5YWx5ZKM5Zu95LiH5bKB77yB
System.out.println(new String(Base64.decode(base64String), "utf-8"));
}
得到明文:中华人民共和国万岁!
注意:本文示例的算法中,没有加入对数字和及一些常用字符的加密支持,如果输入的明文是纯数字串,将不能起到加密效果。当然,你完全可以修改以上代码,加入对Base64所有64个基本字符的支持。