当前位置:   article > 正文

加密算法学习(一、中、1)——传统加密算法(playfair密码)_playfair加密算法

playfair加密算法

本博文借鉴自书本《密码编码学与网络安全——原理与实践(第七版)》,由William Stallings著,王后珍、李莉等译。


参考博客:信息安全-1:python之playfair密码算法详解[原创] - 张玉宝 - 博客园

参考论文:


 

二、代替技术

3.playfair密码

(1)例子:

最著名的多字母密码是playfair密码,他把明文中的双字母音节作为一个单元并将其转换成密文的“双字母音节”。playfair算法是基于一个由密钥词构成的5x5字母矩阵。下面的例子由Lord Peter Wimsey在Dorothy Sayers所著的Have His Carcase一书中给出。

MONAR
CHYBD
EFGI/JK
LPQST
UVWXZ

本例使用的密钥词是monarchy,填充矩阵的方法是:首先将密钥词(去掉重复字母)从左至右、从上至下填在矩阵格子中,再将剩余的字母按照字母表的顺序从左至右、从上至下填在矩阵剩下的格子里。字母I和J暂且当成一个字母。对明文按照如下规则一次加密两个字母:

  • 如果该字母对的两个字母是相同的,那么在他们之间加一个填充字母,比如x。例如balloon先把它变成ba lx lo on这样四个字母对。
  • 落在矩阵同一行的明文字母对中的字母由其右边的字母来代替,每行中最右边的一个字母就落在该列中最左边的第一个字母来代替,比如ar变成RM。
  • 落在矩阵同一列的明文字母对中的字母由其下面的字母来代替,每行最下面的一个字母就落在该列中最上面的第一个字母来代替,比如mu变成CM。
  • 其他的每组明文字母对中的字母按如下方式代替,该字母所在行为密文所在行,另一个字母所在列为密文所在列。比如hs变成BP,ea变成IM(或JM)。

(2)前提约定

首先playfair密码算法适用于对解密后的内容大小写不敏感,因为密文全为大写,无法得知明文的大小写情况,解密的结果全为大写或小写。然后由于密钥中I和J被视为同一个,即明文中出现的I/J都将被被同时视为I或J来加密,那么解密出来的结果就具有二义性。而且当明文中出现两个字符相同的一组字母对时,在它们之间插入的填充字母也有可能出现在明文中。鉴于上面出现的二义性,做出如下约定:

  • 密钥均为小写.
  • 密钥中同时出现i和j时,我用i代替j(当然你也可以用j代替i).
  • 对于明文中两个字符相同的一组字母对,我在它们之间插入大写字母'Z',然后得到的明文长度若为奇数,则在末尾增加大写字母Z(你可以用X或Q这两个出现频率也比较低的字母代替).
  • 解密的结果全为小写.
  • 明文中不能有字母'j'或'J',否则结果具有二义性,因为约定中用i代替了'j',所以解密后的到的'I'不知道是对应'i'还是'j'
  • 不存在明文长度为偶数时,明文从后向前数时,连续地'z'或'Z'字母只有一个。例如,明文"YZ"和"Y"加密前均变为"YZ",那么解密得到的"YZ"不知道对应明文"YZ"还是明文"Y",会具有二义性.
  • 明文中不存在一种情况:'z'或'Z'出现在第偶数位,且其前后字符为不等于‘z’或‘Z’的相同字符,但明文末尾连续多个'z'或'Z'(超过一个)可以出现。

做出如上约定后,加密解密的过程中才不会出现计算机所无法处理的二义性。下面是对加密解密过程做详细分析,以及对前提约定的解释。

(3)过程详解

a、处理密钥

  1. /**
  2. * <h1>得到无重复字母的字符串</h1>
  3. * <br>String str:字符串
  4. * */
  5. private String getNoRepetionStr(String str) {
  6. String noRepetitionKey = " ";
  7. for (int i = 0; i < str.length(); i++) {
  8. int count = 0;
  9. for (int j = 0; j < noRepetitionKey.length(); j++) {
  10. if (str.charAt(i) != noRepetitionKey.charAt(j)) {
  11. count++;
  12. }
  13. }
  14. if (count == noRepetitionKey.length()) {
  15. noRepetitionKey += str.charAt(i);
  16. }
  17. }
  18. return noRepetitionKey.trim();
  19. }

利用上面程序中的getNoRepetionStr()方法,得到无重复的密钥字符串。该方法的思想是,设置变量noRepetionKey等于由单独一个空格组成的字符串,可以处理密钥中存在空格的情况。

在遍历密钥字符串时,若当前字符在noRepetionKey中未出现过,则将其增加到noRepetionKey的末尾。

用count变量来记录noRepetionKey中与当前字符不相等的个数,若得到的count值等于noRepetionKey的length,则代表该字符未出现过。

 b、得到矩阵

  1. /**
  2. * <h1>根据无重复密钥得到填充完整的矩阵一维字符串</h1>
  3. * <br>String noRepetitionStr:无重复的小写密钥字符串
  4. * */
  5. private String getMatrixStr(String noRepetitionKey) {
  6. noRepetitionKey = noRepetitionKey.toUpperCase(); //将无重复的密钥字符串全部转换为大写
  7. noRepetitionKey += " "; //加空格是为了在密钥为空时也能生成矩阵
  8. if (noRepetitionKey.length() < 25) { //如果无重复的密钥字符串长度小于25,即没有包含所有字母(除去'J')
  9. //填充密钥中未出现的字母
  10. for (int i = 0; i < 26 ; i++) {
  11. int count = 0;
  12. char c = (char)('A' + i);
  13. for (int j = 0; j < noRepetitionKey.length(); j++) {
  14. if (c != noRepetitionKey.charAt(j)) {
  15. count++;
  16. }
  17. }
  18. if (count == noRepetitionKey.length()) {
  19. noRepetitionKey = noRepetitionKey.trim() + c;
  20. }
  21. }
  22. //除去填充的字符'J'
  23. noRepetitionKey = noRepetitionKey.split("J")[0] + noRepetitionKey.split("J")[1];
  24. }
  25. return noRepetitionKey;//填充完整的矩阵一维字符串
  26. }

上面程序中的getMatrixStr()方法作用是根据无重复的密钥字符串,和字母表顺序填充矩阵中剩余位置,得到填充完整的矩阵一维字符串。

无重复密钥字符串长度为25(去掉了'j',所以是25)时,即noRepetitionKey.length() == 25时,代表无重复密钥字符串已将矩阵填充完整,无需再用字母表填充。

若其长度小于25,则代表需要用字母表顺序填充,字母填充的结果中包含‘J’,所以要去除字母'J'

  1. /**
  2. * @author GDUYT
  3. * <h1>矩阵中的每个元素单元</h1>
  4. * */
  5. class MatrixUnit {
  6. int row; //行值,取值空间为[0,4]
  7. int column; //列值,取值空间为[0,4]
  8. char UnitChar; //数值
  9. public MatrixUnit() {
  10. }
  11. public MatrixUnit(int row, int column, char unitChar) {
  12. this.row = row;
  13. this.column = column;
  14. UnitChar = unitChar;
  15. }
  16. @Override
  17. public String toString() {
  18. return UnitChar + "";
  19. }
  20. }

上面程序的功能即定义矩阵单元类,其属性包含该矩阵单元在矩阵中的行值、列值、数值。

  1. /**
  2. * <h1>得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的行值、列值、数值</h1>
  3. *<br> String matrixStr:一维的25位密钥矩阵字符串
  4. * */
  5. private MatrixUnit[] getMatrix(String matrixStr) {
  6. MatrixUnit matrixUnit[] = new MatrixUnit[25];
  7. for (int i = 0; i < 25; i++) {
  8. matrixUnit[i] = new MatrixUnit(i/5, i%5, matrixStr.charAt(i));
  9. }
  10. return matrixUnit;
  11. }

 上述程序的功能即为根据前面得到的矩阵一维字符串,得到每个字符单元在矩阵中的元数据,然后组成矩阵单元类的一维数组。

c、处理明文

  1. /**
  2. * <h1>处理加密前的明文</h1>
  3. * <br>String plaintextOperate:明文
  4. * */
  5. private String dealPlaintextBeforeEncrypt(String plaintext) {
  6. //明文全部转换为小写,并用字符'i'替换字符'j'
  7. plaintext = plaintext.toLowerCase().replace('j', 'i');
  8. //对于明文中两个一样的一组字母对,在其中间插入大写字母'Z'
  9. for (int i = 1; i < plaintext.length(); i+=2) {
  10. if (plaintext.charAt(i-1) == plaintext.charAt(i)) {
  11. StringBuilder sb = new StringBuilder();
  12. for (int j = 0; j < i; j++) {
  13. sb.append(plaintext.charAt(j));
  14. }
  15. sb.append("Z");
  16. for (int j = i; j < plaintext.length(); j++) {
  17. sb.append(plaintext.charAt(j));
  18. }
  19. plaintext = sb.toString();
  20. i=1;
  21. }
  22. }
  23. //对于明文长度为奇数时在末尾增加大写字母'Z'
  24. if ((plaintext.length() % 2) == 1) {
  25. plaintext += "Z";
  26. }
  27. return plaintext.toUpperCase(); //将明文全部转换为大写
  28. }

上面程序中的dealPlaintextBeforeEncrypt()方法,其功能即为处理加密前的明文。首先需要将所有的明文转换为小写,然后用'i'代替明文中的所有的字符‘j’。

对于明文中两个一样的一组字母对,在其中间插入大写字母'Z',因为明文都转换为了小写,所以‘Z’一定出现在偶数位上。遍历时,只需要检测偶数位的字符是否和前一位相同,相同则插入大写字母'Z'。

最终得到的明文长度若为奇数,则在其末尾增加字符'Z'。

d、加密

  1. /**
  2. * <h1>根据明文和密钥矩阵加密得到密文</h1>
  3. * <br>String plaintext:处理后的明文
  4. * <br>MatrixUnit[] matrix:密钥矩阵
  5. * */
  6. private String encryptOperate(String plaintext, MatrixUnit[] matrix) {
  7. StringBuilder sb = new StringBuilder();
  8. for (int i = 0; i < plaintext.length(); i += 2) {
  9. MatrixUnit plainUnit1 = new MatrixUnit();
  10. MatrixUnit plainUnit2 = new MatrixUnit();
  11. //得到两个一组的明文字符在矩阵中的位置"元数据"
  12. for (int j = 0; j < matrix.length; j++) {
  13. if (plaintext.charAt(i) == matrix[j].UnitChar) {
  14. plainUnit1 = matrix[j];
  15. }
  16. if (plaintext.charAt(i+1) == matrix[j].UnitChar) {
  17. plainUnit2 = matrix[j];
  18. }
  19. }
  20. //根据两个一组的明文在矩阵中的相对位置对其加密
  21. if (plainUnit1.row == plainUnit2.row) { //如果两个明文在同一行
  22. plainUnit1 = matrix[plainUnit1.row*5 + (plainUnit1.column+1)%5];
  23. plainUnit2 = matrix[plainUnit2.row*5 + (plainUnit2.column+1)%5];
  24. } else if (plainUnit1.column == plainUnit2.column) { //如果两个明文在同一列
  25. plainUnit1 = matrix[((plainUnit1.row+1)%5)*5 + plainUnit1.column];
  26. plainUnit2 = matrix[((plainUnit2.row+1)%5)*5 + plainUnit2.column];
  27. } else { //两个明文既不在同一行,也不在同一列
  28. MatrixUnit tempUnit = plainUnit1;
  29. plainUnit1 = matrix[plainUnit1.row*5 + plainUnit2.column];
  30. plainUnit2 = matrix[plainUnit2.row*5 + tempUnit.column];
  31. }
  32. sb.append(plainUnit1.toString() + plainUnit2.toString());
  33. }
  34. return sb.toString();
  35. }

上面的程序中的encryptOperate()方法,功能为根据处理后的明文和密钥矩阵进行加密,即为加密的核心操作。

先得到明文中一对字母对在矩阵中对应的行值、列值这些矩阵单元“元数据”。然后根据字母对在矩阵中的相对位置遵循变换规则进行变换。

  1. /**
  2. * <h1>加密方法</h1>
  3. * <br>String plaintext:明文字符串
  4. * <br>String key:密钥(小写字母组成的字符串)
  5. * */
  6. public String encrypt(String plaintext, String key) {
  7. //处理密钥
  8. //将密钥中出现的'j'字符用'i'代替
  9. String noRepetitionKey = key.replace("j", "i");
  10. //得到密钥的无重复字符串
  11. noRepetitionKey = getNoRepetionStr(noRepetitionKey);
  12. //得到填充完整的全大写的矩阵一维字符串
  13. String matrixStr = getMatrixStr(noRepetitionKey);
  14. //得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的元数据
  15. MatrixUnit[] matrix = getMatrix(matrixStr);
  16. System.out.println("密钥矩阵:");
  17. for (int i = 0; i < matrix.length; i++) {
  18. System.out.print(matrix[i].toString());
  19. if (i % 5 == 4) {
  20. System.out.println();
  21. }
  22. }
  23. //处理加密前的明文
  24. plaintext = dealPlaintextBeforeEncrypt(plaintext);
  25. System.out.println("处理后的明文:[" + plaintext + "]");
  26. //加密
  27. String ciphertext = encryptOperate(plaintext, matrix);
  28. return ciphertext;
  29. }

上面程序即为对前面那些方法的调用,形成供外部调用的同一的encrypt(String plaintext, String key)方法接口。

e、解密

加密完成之后,解密就变得简单的,因为都是加密的逆过程,但每个步骤都不能少。

  1. /**
  2. * <h1>根据密文和密钥矩阵得到明文</h1>
  3. * <br>String ciphertext:密文字符串(大写字母组成的字符串,长度为偶数个)
  4. * <br>MatrixUnit[] matrix:密钥矩阵
  5. * */
  6. public String decryptOperate(String ciphertext, MatrixUnit[] matrix) {
  7. StringBuilder sb = new StringBuilder();
  8. for (int i = 0; i < ciphertext.length(); i += 2) {
  9. MatrixUnit plainUnit1 = new MatrixUnit();
  10. MatrixUnit plainUnit2 = new MatrixUnit();
  11. //得到两个一组的密文字符在矩阵中的位置"元数据"
  12. for (int j = 0; j < matrix.length; j++) {
  13. if (ciphertext.charAt(i) == matrix[j].UnitChar) {
  14. plainUnit1 = matrix[j];
  15. }
  16. if (ciphertext.charAt(i+1) == matrix[j].UnitChar) {
  17. plainUnit2 = matrix[j];
  18. }
  19. }
  20. //根据两个一组的密文在矩阵中的相对位置对其解密
  21. if (plainUnit1.row == plainUnit2.row) { //如果两个密文在同一行
  22. plainUnit1 = matrix[plainUnit1.row*5 + (plainUnit1.column-1+5)%5];
  23. plainUnit2 = matrix[plainUnit2.row*5 + (plainUnit2.column-1+5)%5];
  24. } else if (plainUnit1.column == plainUnit2.column) { //如果两个密文在同一列
  25. plainUnit1 = matrix[((plainUnit1.row-1+5)%5)*5 + plainUnit1.column];
  26. plainUnit2 = matrix[((plainUnit2.row-1+5)%5)*5 + plainUnit2.column];
  27. } else { //两个密文既不在同一行,也不在同一列
  28. MatrixUnit tempUnit = plainUnit1;
  29. plainUnit1 = matrix[plainUnit1.row*5 + plainUnit2.column];
  30. plainUnit2 = matrix[plainUnit2.row*5 + tempUnit.column];
  31. }
  32. sb.append(plainUnit1.toString() + plainUnit2.toString());
  33. }
  34. return sb.toString();
  35. }

首先就是根据密文进行解密,上面程序中的decryptOperate()方法的过程即为encryptOperate()方法的逆过程。

  1. /**
  2. * <h1>处理解密后的明文</h1>
  3. * <br>String plaintext:解密后的明文
  4. * */
  5. private String dealPlaintextAfterDecrypt(String plaintext) {
  6. //末尾存在字符'Z'时的处理
  7. //如果末尾存在字符'Z',则用空格代替它,即删除它但保留字符串长度
  8. if (plaintext.charAt(plaintext.length()-1) == 'Z') {
  9. plaintext = plaintext.substring(0, plaintext.length()-1) + " ";
  10. }
  11. //去除加密时连续重复字母之间加入的'Z',其特点为一定出现在偶数位上,且其前后字母相同
  12. for (int i = plaintext.length() - 3; i > 0; i -= 2) {
  13. if (plaintext.charAt(i) == 'Z'
  14. && (plaintext.charAt(i-1) == plaintext.charAt(i+1))) {
  15. StringBuilder sb = new StringBuilder();
  16. for (int j = 0; j < i; j++) {
  17. sb.append(plaintext.charAt(j));
  18. }
  19. for (int j = i + 1; j < plaintext.length(); j++) {
  20. sb.append(plaintext.charAt(j));
  21. }
  22. plaintext = sb.toString();
  23. }
  24. }
  25. return plaintext.toLowerCase().trim();
  26. }

然后便需要对解密后的结构进行处理,上面程序中,dealPlaintextAfterDecrypt()方法即为处理解密后的结果。

第一步是除去末尾填充的大写字母"Z",用空格代替它,即保留其字符长度。根据前面约定的第六条:

  • 不存在明文长度为偶数时,明文从后向前数时,连续地'z'或'Z'字母只有一个。例如,明文"YZ"和"Y"加密前均变为"YZ",那么解密得到的"YZ"不知道对应明文"YZ"还是明文"Y",会具有二义性.

即不存在单独的'z'或'Z'出现在明文末尾,那么解密后的结果末尾出现的大写字母"Z",一定是因为加密时长度为奇数所增加的"Z",那么这个"Z"一定需要删除。例如:(注意:此时的解密后的结果长度一定是偶数,因为密文长度为偶数)

解密后:"YZ" ——> "Y"(对应明文)

解密后:"ZZ" ——> "Z" (对应明文)

解密后:"YZZZ" ——> "YZZ" (对应明文)

解密后:"ZZZZ" ——> "ZZ"(对应明文)

解密后:"YZZZZZ" ——> "YZZZ"(对应明文)

解密后:"ZZZZZZ" ——> "ZZZ"(对应明文)

…………

 

第二步是去除加密时连续重复字母之间加入的大写字母'Z',其特点为一定出现在偶数位上,且其前后字母相同。根据前面约定的最后一条:

  • 明文中不存在一种情况:'z'或'Z'出现在第偶数位,且其前后字符为不等于‘z’或‘Z’的相同字符,但明文末尾连续多个'z'或'Z'(超过一个)可以出现。

这是因为存在一种情况:当明为"HZHY"或"HHY"时,加密前对明文处理后得到的结果都是"HZHY",那么经过加密和解密后,得到的待处理结构也都是"HZHY",此时将无法得知对应的明文是"HZHY"还是"HHY",即该"Z"可能是原文中的,也可能是增加的。所以做出上面的约定,去除二义性。

去除"Z"的过程借鉴自博客:信息安全-1:python之playfair密码算法详解[原创] - 张玉宝 - 博客园中的从后向前删除的思想。因为前面去除末尾"Z"的时候并未改变字符长度,故其长度还是为偶数。从倒数第三位开始检测,如果当前字符为"Z",且其前后字符相同,则删除这个"Z"。

比如:解密后得到的"ZZZZ",去除末尾"Z",代替为空格,得到"ZZZ "(末尾有一个空格),其长度依旧为4。从其到时第三个开始检测,即下标为1的第二个字符"Z",其前后字符均为"Z",则删除这个"Z",得到"ZZ "(末尾有一个空格),程序中步长为i  -= 2,因为1 - 2 = -1 < 0,所以循环结束。再去除末尾空格,变为全小写,得到正确明文"zz"。

再比如:解密后得到的"YZZZZZZZ",去除末尾"Z",代替为空格,得到"YZZZZZZ "(末尾有一个空格),其长度依旧为8。从其到时第三个开始检测,即下标为5的第六个字符"Z",其前后字符均为"Z",则删除这个"Z",得到"YZZZZZ "(末尾有一个空格),程序中i = 5 - 2 = 3 > 0,循环继续。

检测下标为3的第四个字符"Z",其前后字符均为"Z",则删除这个"Z",得到"YZZZZ "(末尾有一个空格),程序中i = 3 - 2 = 1 > 0,但下标为1的第二个字符"Z"前后字符不相同,所以不做删除。程序中i = 1 - 2 = -1 < 0,循环结束。进行后续操作后,得到正确明文"yzzzz"。

  1. /**
  2. * <h1>解密方法</h1>
  3. * <br>String ciphertext:密文字符串(大写字母组成的字符串,长度为偶数个)
  4. * <br>String key:密钥(小写字母组成的字符串)
  5. * */
  6. public String decrypt(String ciphertext, String key) {
  7. //处理密钥
  8. //将密钥中出现的'j'字符用'i'代替
  9. String noRepetitionKey = key.replace("j", "i");
  10. //得到密钥的无重复字符串
  11. noRepetitionKey = getNoRepetionStr(noRepetitionKey);
  12. //得到填充完整的全大写的矩阵一维字符串
  13. String matrixStr = getMatrixStr(noRepetitionKey);
  14. //得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的元数据
  15. MatrixUnit[] matrix = getMatrix(matrixStr);
  16. //解密
  17. String plaintext = decryptOperate(ciphertext, matrix);
  18. //处理解密后的明文
  19. plaintext = dealPlaintextAfterDecrypt(plaintext);
  20. return plaintext;
  21. }

上面程序即为对前面那些方法的调用,形成供外部调用的同一的decrypt(String ciphertext, String key)方法接口。


下面是两个完整测试输出:

  1. //测试1:
  2. public static void main(String[] args) throws IOException {
  3. String plaintext = FileOperate.ReadIn
  4. ("D:/programming/java/mec/java_ee/code/EncryptionAlgorithm/src/test/plaintext.txt");
  5. System.out.println("明文:[" + plaintext + "]");
  6. Playfair pf = new Playfair();
  7. String key = " ";
  8. System.out.println("密钥:[" + key + "]");
  9. String ciphertext = pf.encrypt(plaintext, key);
  10. System.out.println("加密后的密文:[" + ciphertext + "]");
  11. String plaintext1 = pf.decrypt(ciphertext, key);
  12. System.out.println("解密后的结果:[" + plaintext1 + "]");
  13. }
  14. //输出:
  15. 明文:[zzzzfhfijfhzzzzzz]
  16. 密钥:[ ]
  17. 密钥矩阵:
  18. ABCDE
  19. FGHIK
  20. LMNOP
  21. QRSTU
  22. VWXYZ
  23. 处理后的明文:[ZZZZZZZFHFIZIFHZZZZZZZZZZZ]
  24. 加密后的密文:[VVVVVVVKIGKYKGKXVVVVVVVVVV]
  25. 解密后的结果:[zzzzfhfiifhzzzzzz]
  1. //测试2:
  2. public static void main(String[] args) throws IOException {
  3. String plaintext = FileOperate.ReadIn
  4. ("D:/programming/java/mec/java_ee/code/EncryptionAlgorithm/src/test/plaintext.txt");
  5. System.out.println("明文:[" + plaintext + "]");
  6. Playfair pf = new Playfair();
  7. String key = "playfair";
  8. System.out.println("密钥:[" + key + "]");
  9. String ciphertext = pf.encrypt(plaintext, key);
  10. System.out.println("加密后的密文:[" + ciphertext + "]");
  11. String plaintext1 = pf.decrypt(ciphertext, key);
  12. System.out.println("解密后的结果:[" + plaintext1 + "]");
  13. }
  14. //输出:
  15. 明文:[abcdefghiiklmnopqrstuvwxyzz]
  16. 密钥:[playfair]
  17. 密钥矩阵:
  18. PLAYF
  19. IRBCD
  20. EGHKM
  21. NOQST
  22. UVWXZ
  23. 处理后的明文:[ABCDEFGHIZIKLMNOPQRSTUVWXYZZZZ]
  24. 加密后的密文:[BHDIMPHKDUCEFGOQANCONZWXYCUUUU]
  25. 解密后的结果:[abcdefghiiklmnopqrstuvwxyzz]

完整代码如下:

  1. package test;
  2. import java.io.IOException;
  3. import algorithm.Playfair;
  4. import util.FileOperate;
  5. public class PlayfairTest {
  6. public static void main(String[] args) throws IOException {
  7. String plaintext = FileOperate.ReadIn
  8. ("D:/programming/java/mec/java_ee/code/EncryptionAlgorithm/src/test/plaintext.txt");
  9. System.out.println("明文:[" + plaintext + "]");
  10. Playfair pf = new Playfair();
  11. String key = "playfair";
  12. System.out.println("密钥:[" + key + "]");
  13. String ciphertext = pf.encrypt(plaintext, key);
  14. System.out.println("加密后的密文:[" + ciphertext + "]");
  15. String plaintext1 = pf.decrypt(ciphertext, key);
  16. System.out.println("解密后的结果:[" + plaintext1 + "]");
  17. }
  18. }
  1. package util;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. /**
  5. * @author GDUYT
  6. * 文件操作
  7. * */
  8. public class FileOperate {
  9. /**
  10. * 读取文件内容
  11. * @param 明文路径地址
  12. * */
  13. public static String ReadIn(String plaintextAdd) throws IOException {
  14. FileInputStream fin = new FileInputStream(plaintextAdd);
  15. int len;
  16. StringBuilder sb = new StringBuilder();
  17. while ((len = fin.read()) != -1) {
  18. sb.append((char)len);
  19. }
  20. fin.close();
  21. return sb.toString();
  22. }
  23. }
  1. //D:/programming/java/mec/java_ee/code/EncryptionAlgorithm/src/test/plaintext.txt
  2. abcdefghiiklmnopqrstuvwxyzz
  1. package algorithm;
  2. /**
  3. * @author GDUYT
  4. * <h1>playfair加密(适用于对解密后的内容大小写不敏感,因为密文全为大写,无法得知明文的大小写情况,解密的结果全为大写或小写)</h1>
  5. * <h2>约定:</h2>
  6. * <br>(1)密钥均为小写
  7. * <br>(2)密钥中同时出现i和j时,用i代替j
  8. * <br>(3)对于明文中两个字符相同的一组字母对,在它们之间插入大写字母'Z',然后得到的明文长度若为奇数,则在末尾增加大写字母Z
  9. * <br>(4)解密的结果全为小写
  10. * <br>(5)明文中不能有字母'j'或'J',否则结果具有二义性,因为约定中用i代替了'j',所以解密后的到的'I'不知道是对应'i'还是'j'
  11. * <br>(6)不存在明文长度为偶数时,明文从后向前数,连续地'z'或'Z'字母只有一个,因为例如明文"YZ"和"Y"加密后均为"YZ",那么解密时具有二义性
  12. * <br>(7)明文中不存在一种情况:'z'或'Z'出现在第偶数位,且其前后字符为不等于‘z’或‘Z’的相同字符,但明文末尾连续多个'z'或'Z'(超过一个)可以出现。
  13. * */
  14. public class Playfair {
  15. /**
  16. * <h1>加密方法</h1>
  17. * <br>String plaintext:明文字符串
  18. * <br>String key:密钥(小写字母组成的字符串)
  19. * */
  20. public String encrypt(String plaintext, String key) {
  21. //处理密钥
  22. //将密钥中出现的'j'字符用'i'代替
  23. String noRepetitionKey = key.replace("j", "i");
  24. //得到密钥的无重复字符串
  25. noRepetitionKey = getNoRepetionStr(noRepetitionKey);
  26. //得到填充完整的全大写的矩阵一维字符串
  27. String matrixStr = getMatrixStr(noRepetitionKey);
  28. //得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的元数据
  29. MatrixUnit[] matrix = getMatrix(matrixStr);
  30. System.out.println("密钥矩阵:");
  31. for (int i = 0; i < matrix.length; i++) {
  32. System.out.print(matrix[i].toString());
  33. if (i % 5 == 4) {
  34. System.out.println();
  35. }
  36. }
  37. //处理加密前的明文
  38. plaintext = dealPlaintextBeforeEncrypt(plaintext);
  39. System.out.println("处理后的明文:[" + plaintext + "]");
  40. //加密
  41. String ciphertext = encryptOperate(plaintext, matrix);
  42. return ciphertext;
  43. }
  44. /**
  45. * <h1>解密方法</h1>
  46. * <br>String ciphertext:密文字符串(大写字母组成的字符串,长度为偶数个)
  47. * <br>String key:密钥(小写字母组成的字符串)
  48. * */
  49. public String decrypt(String ciphertext, String key) {
  50. //处理密钥
  51. //将密钥中出现的'j'字符用'i'代替
  52. String noRepetitionKey = key.replace("j", "i");
  53. //得到密钥的无重复字符串
  54. noRepetitionKey = getNoRepetionStr(noRepetitionKey);
  55. //得到填充完整的全大写的矩阵一维字符串
  56. String matrixStr = getMatrixStr(noRepetitionKey);
  57. //得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的元数据
  58. MatrixUnit[] matrix = getMatrix(matrixStr);
  59. //解密
  60. String plaintext = decryptOperate(ciphertext, matrix);
  61. //处理解密后的明文
  62. plaintext = dealPlaintextAfterDecrypt(plaintext);
  63. return plaintext;
  64. }
  65. /**
  66. * <h1>根据密文和密钥矩阵得到明文</h1>
  67. * <br>String ciphertext:密文字符串(大写字母组成的字符串,长度为偶数个)
  68. * <br>MatrixUnit[] matrix:密钥矩阵
  69. * */
  70. public String decryptOperate(String ciphertext, MatrixUnit[] matrix) {
  71. StringBuilder sb = new StringBuilder();
  72. for (int i = 0; i < ciphertext.length(); i += 2) {
  73. MatrixUnit plainUnit1 = new MatrixUnit();
  74. MatrixUnit plainUnit2 = new MatrixUnit();
  75. //得到两个一组的密文字符在矩阵中的位置"元数据"
  76. for (int j = 0; j < matrix.length; j++) {
  77. if (ciphertext.charAt(i) == matrix[j].UnitChar) {
  78. plainUnit1 = matrix[j];
  79. }
  80. if (ciphertext.charAt(i+1) == matrix[j].UnitChar) {
  81. plainUnit2 = matrix[j];
  82. }
  83. }
  84. //根据两个一组的密文在矩阵中的相对位置对其解密
  85. if (plainUnit1.row == plainUnit2.row) { //如果两个密文在同一行
  86. plainUnit1 = matrix[plainUnit1.row*5 + (plainUnit1.column-1+5)%5];
  87. plainUnit2 = matrix[plainUnit2.row*5 + (plainUnit2.column-1+5)%5];
  88. } else if (plainUnit1.column == plainUnit2.column) { //如果两个密文在同一列
  89. plainUnit1 = matrix[((plainUnit1.row-1+5)%5)*5 + plainUnit1.column];
  90. plainUnit2 = matrix[((plainUnit2.row-1+5)%5)*5 + plainUnit2.column];
  91. } else { //两个密文既不在同一行,也不在同一列
  92. MatrixUnit tempUnit = plainUnit1;
  93. plainUnit1 = matrix[plainUnit1.row*5 + plainUnit2.column];
  94. plainUnit2 = matrix[plainUnit2.row*5 + tempUnit.column];
  95. }
  96. sb.append(plainUnit1.toString() + plainUnit2.toString());
  97. }
  98. return sb.toString();
  99. }
  100. /**
  101. * <h1>根据明文和密钥矩阵加密得到密文</h1>
  102. * <br>String plaintext:处理后的明文
  103. * <br>MatrixUnit[] matrix:密钥矩阵
  104. * */
  105. private String encryptOperate(String plaintext, MatrixUnit[] matrix) {
  106. StringBuilder sb = new StringBuilder();
  107. for (int i = 0; i < plaintext.length(); i += 2) {
  108. MatrixUnit plainUnit1 = new MatrixUnit();
  109. MatrixUnit plainUnit2 = new MatrixUnit();
  110. //得到两个一组的明文字符在矩阵中的位置"元数据"
  111. for (int j = 0; j < matrix.length; j++) {
  112. if (plaintext.charAt(i) == matrix[j].UnitChar) {
  113. plainUnit1 = matrix[j];
  114. }
  115. if (plaintext.charAt(i+1) == matrix[j].UnitChar) {
  116. plainUnit2 = matrix[j];
  117. }
  118. }
  119. //根据两个一组的明文在矩阵中的相对位置对其加密
  120. if (plainUnit1.row == plainUnit2.row) { //如果两个明文在同一行
  121. plainUnit1 = matrix[plainUnit1.row*5 + (plainUnit1.column+1)%5];
  122. plainUnit2 = matrix[plainUnit2.row*5 + (plainUnit2.column+1)%5];
  123. } else if (plainUnit1.column == plainUnit2.column) { //如果两个明文在同一列
  124. plainUnit1 = matrix[((plainUnit1.row+1)%5)*5 + plainUnit1.column];
  125. plainUnit2 = matrix[((plainUnit2.row+1)%5)*5 + plainUnit2.column];
  126. } else { //两个明文既不在同一行,也不在同一列
  127. MatrixUnit tempUnit = plainUnit1;
  128. plainUnit1 = matrix[plainUnit1.row*5 + plainUnit2.column];
  129. plainUnit2 = matrix[plainUnit2.row*5 + tempUnit.column];
  130. }
  131. sb.append(plainUnit1.toString() + plainUnit2.toString());
  132. }
  133. return sb.toString();
  134. }
  135. /**
  136. * <h1>处理解密后的明文</h1>
  137. * <br>String plaintext:解密后的明文
  138. * */
  139. private String dealPlaintextAfterDecrypt(String plaintext) {
  140. //末尾存在字符'Z'时的处理
  141. //如果末尾存在字符'Z',则用空格代替它,即删除它但保留字符串长度
  142. if (plaintext.charAt(plaintext.length()-1) == 'Z') {
  143. plaintext = plaintext.substring(0, plaintext.length()-1) + " ";
  144. }
  145. //去除加密时连续重复字母之间加入的'Z',其特点为一定出现在偶数位上,且其前后字母相同
  146. for (int i = plaintext.length() - 3; i > 0; i -= 2) {
  147. if (plaintext.charAt(i) == 'Z'
  148. && (plaintext.charAt(i-1) == plaintext.charAt(i+1))) {
  149. StringBuilder sb = new StringBuilder();
  150. for (int j = 0; j < i; j++) {
  151. sb.append(plaintext.charAt(j));
  152. }
  153. for (int j = i + 1; j < plaintext.length(); j++) {
  154. sb.append(plaintext.charAt(j));
  155. }
  156. plaintext = sb.toString();
  157. }
  158. }
  159. return plaintext.toLowerCase().trim();
  160. }
  161. /**
  162. * <h1>处理加密前的明文</h1>
  163. * <br>String plaintextOperate:明文
  164. * */
  165. private String dealPlaintextBeforeEncrypt(String plaintext) {
  166. //明文全部转换为小写,并用字符'i'替换字符'j'
  167. plaintext = plaintext.toLowerCase().replace('j', 'i');
  168. //对于明文中两个一样的一组字母对,在其中间插入大写字母'Z'
  169. for (int i = 1; i < plaintext.length(); i+=2) {
  170. if (plaintext.charAt(i-1) == plaintext.charAt(i)) {
  171. StringBuilder sb = new StringBuilder();
  172. for (int j = 0; j < i; j++) {
  173. sb.append(plaintext.charAt(j));
  174. }
  175. sb.append("Z");
  176. for (int j = i; j < plaintext.length(); j++) {
  177. sb.append(plaintext.charAt(j));
  178. }
  179. plaintext = sb.toString();
  180. i=1;
  181. }
  182. }
  183. //对于明文长度为奇数时在末尾增加大写字母'Z'
  184. if ((plaintext.length() % 2) == 1) {
  185. plaintext += "Z";
  186. }
  187. return plaintext.toUpperCase(); //将明文全部转换为大写
  188. }
  189. /**
  190. * <h1>得到一维密钥矩阵字符串中每个单元在二维5x5字符矩阵中的行值、列值、数值</h1>
  191. *<br> String matrixStr:一维的25位密钥矩阵字符串
  192. * */
  193. private MatrixUnit[] getMatrix(String matrixStr) {
  194. MatrixUnit matrixUnit[] = new MatrixUnit[25];
  195. for (int i = 0; i < 25; i++) {
  196. matrixUnit[i] = new MatrixUnit(i/5, i%5, matrixStr.charAt(i));
  197. }
  198. return matrixUnit;
  199. }
  200. /**
  201. * @author GDUYT
  202. * <h1>矩阵中的每个元素单元</h1>
  203. * */
  204. class MatrixUnit {
  205. int row; //行值,取值空间为[0,4]
  206. int column; //列值,取值空间为[0,4]
  207. char UnitChar; //数值
  208. public MatrixUnit() {
  209. }
  210. public MatrixUnit(int row, int column, char unitChar) {
  211. this.row = row;
  212. this.column = column;
  213. UnitChar = unitChar;
  214. }
  215. @Override
  216. public String toString() {
  217. return UnitChar + "";
  218. }
  219. }
  220. /**
  221. * <h1>根据无重复密钥得到填充完整的矩阵一维字符串</h1>
  222. * <br>String noRepetitionStr:无重复的小写密钥字符串
  223. * */
  224. private String getMatrixStr(String noRepetitionKey) {
  225. noRepetitionKey = noRepetitionKey.toUpperCase(); //将无重复的密钥字符串全部转换为大写
  226. noRepetitionKey += " "; //加空格是为了在密钥为空时也能生成矩阵
  227. if (noRepetitionKey.length() < 25) { //如果无重复的密钥字符串长度小于25,即没有包含所有字母(除去'J')
  228. //填充密钥中未出现的字母
  229. for (int i = 0; i < 26 ; i++) {
  230. int count = 0;
  231. char c = (char)('A' + i);
  232. for (int j = 0; j < noRepetitionKey.length(); j++) {
  233. if (c != noRepetitionKey.charAt(j)) {
  234. count++;
  235. }
  236. }
  237. if (count == noRepetitionKey.length()) {
  238. noRepetitionKey = noRepetitionKey.trim() + c;
  239. }
  240. }
  241. //除去填充的字符'J'
  242. noRepetitionKey = noRepetitionKey.split("J")[0] + noRepetitionKey.split("J")[1];
  243. }
  244. return noRepetitionKey;//填充完整的矩阵一维字符串
  245. }
  246. /**
  247. * <h1>得到无重复字母的字符串</h1>
  248. * <br>String str:字符串
  249. * */
  250. private String getNoRepetionStr(String str) {
  251. String noRepetitionKey = " ";
  252. for (int i = 0; i < str.length(); i++) {
  253. int count = 0;
  254. for (int j = 0; j < noRepetitionKey.length(); j++) {
  255. if (str.charAt(i) != noRepetitionKey.charAt(j)) {
  256. count++;
  257. }
  258. }
  259. if (count == noRepetitionKey.length()) {
  260. noRepetitionKey += str.charAt(i);
  261. }
  262. }
  263. return noRepetitionKey.trim();
  264. }
  265. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/226218?site
推荐阅读
相关标签
  

闽ICP备14008679号