【阿库娅教你X代码】PlayFair密码——0

来源:互联网 发布:音效下载软件 编辑:程序博客网 时间:2024/05/16 11:52

大家好,我是这里写图片描述,据说是司掌水的女神,当然也是美貌与智慧并重。今天要讲的正是密码学课本上大部分弱智的古典密码中稍微有趣一点点而且还挺使用的PlayFair密码!

PlayFair密码算法的主要构成:密钥、PlayFair代换表(PF表)、(约定的)填充字母、加密算法、解密算法

Playfair密码(英文:Playfair cipher 或 Playfair square)是一种替换密码,1854年由查尔斯·惠斯通(Charles Wheatstone)的英国人发明。
PF表是利用密钥填充形成的加密表,而加密规则如下(解密规则逆行之):
- 若p1 p2在同一行,对应密文c1 c2分别是紧靠p1 p2 右端的字母。其中第一列被看做是最后一列的右方。如,按照前表,ct对应dc
- 若p1 p2在同一列,对应密文c1 c2分别是紧靠p1 p2 下方的字母。其中第一行被看做是最后一行的下方。
- 若p1 p2不在同一行,不在同一列,则c1 c2是由p1 p2确定的矩形的其他两角的字母(至于横向替换还是纵向替换要事先约好,或自行尝试)。如,按照前表,wh对应tk或kt。

Notic:

  • I/J是看成同一个,实际这个表中只要25个字母,这种设计阿库娅都能明白,因为26-1刚好得到5x5的矩阵(实际战争中使用时,被去掉的字母是z,因为它出现频率最低)
  • 密文永远是双数,分组后也永远没有哪一组是俩字母相同的!

下面我们先来分解笔算步骤,然后通过程序模拟来实现算法。


Step0.约定好密钥和用来填充的字母,比如

  • 密钥 = abbcdef
  • 约定的字母 = z

Step1.写出PlayFair代换表

显然,不能直接把eddcbaf写进去,因为由一个密钥得到的PF代换表实际是25个字母的一种唯一排序,所以需要去重。然后先填充去重后的密钥,再填充密钥中不包含的字母,得到5X5的矩阵!

  • 去重:edcbaf
  • 填充PF表
e d c b a f g h i/j k l m n o p q r s t u v w x y z

Step2.0 对明文加密

2.0.1:明文预处理

  • 分组&插入
    因为加密运算就是在对两个一组的不同字母的代换,所以先分组,相同的要插入那个
    约定的字母(此例为z,显然一个是不够的,可以让z是默认,还要一个预备的约定字母来顶替遇上“zz”的情况)
  • 补齐
    经过若干次分组&插入后,如果刚好最后一组只有一个字母,就要尾部填充一个约定的字母
  • 显然,被进行上述明文预处理的明文才是真正参与到PF加密、解密的明文。想要彻底还原成原样阿库娅当然知道,就是不告诉你→_→
  • 极端のexample:
    原始明文:zzzz ——>分组:zz zz ->插入:(假设还有个预备的约定字母a)默认字母插入失败,使用预备字母插入 ——>(分组然后插入若干次后) ——>za za za z ——>补齐(也就是真正的明文,简直炫酷,认不出来系列): za za za zj

2.0.2:对明文加密

根据PF的规则,对两个一组的字母的位置进行判断

  • 属同一列,下边的咯

  • 属不同行不同列的,就让这俩字母的行列构成一个四边形,定位得到的对角就是密文,同行的才对应

  • 正常のexample

    • 明文 : 最近的动画到处是暗牧,光腚局我fxxk oo (哔!~)!这里写图片描述

    • 分组:最近 的动 画到 处是 暗牧 光腚 局我 fx xk oo —–其中oo相同,插入z ———变成:fx xk oz o

    • 分组后是单数,补齐:fx xk oz oz
    • 预处理后明文:最近的动画到处是暗牧光腚局我fxxkoofxxkozoz
    • 加密:fx —–在pf表中这俩字母不同行不同列,那就根据其这个画个四边形,找到另外两个对角的字母hv红是f,蓝是x(不是vh,同行才是对应关系)
      如果密文是ed ,那就是同行,密文就是cb(这种是毗邻的密文,要跳过密文,不是dc)。
      同行相似~

Step2.1对密文解密

无非就是对2.0.2的反向操作

  • 同行那就左边咯
  • 同列那就上边咯
  • 不同行不同列?对角定位,同行对应咯(显然,这个操作正反一样的)

是不是很简单?是不是很简单?是不是很简单?
这里写图片描述

代码还没开始写呢。


下面开始把以上笔算步骤翻译成代码
因为是阿库娅,所以注释有点多,代码看不下去先看注释:

正式开始のstep0:初始化必要信息:

<c++>int main(){    string key;        //———————————————————————————— 声明一个字符串,放密钥    string cleartext;    //—————————————————————————— 声明一个字符串,明文    cout << "请输入密钥: " ;getline(cin, key);    cout << "请输入明文: " ;getline(cin, cleartext);    string playfair[5][5] ;  //—————————————————————— 声明空的PF表    string another;   //————————————————————————————  约定填充的字母    cout << "输入约定的填充字母: ";cin >> another; }</c++>

接着我们来看看step1:填充PF代换表

  • 预备
    • 先介绍下下面两个重要的变量:alp[3][26] 以及 abLength
    • abLength就是字母填入PF表的序号`——>代码:int abLength=0;
    • alp你可以可以用字母表来称呼它
      alp[3][26]在我眼里是这样的:(这是初始化状态)
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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

代码:int alp[3][26] = {{0*26},{0*26},{0*26}};//———————初始化为全0,即3x26个0

(注意了,alp这个实际是没有a~z那行的,我用数组下标加上97就刚好得到对应字母的ASCLL码了。也就是alp[][i] 对应 char = i+97

  • 那么它们有啥用呢?

    • 首先,这里step1需要从笔算那里转换一下思路。因为 填入pf表 等于先填入 密钥中的字母 再填入 剩下的字母,所以我们要利用 字母表 第0行的值作为标识,1就是已经填了的、密钥出现过了的,0表示未出现过
    • 其次,第1行和第2行是用来保存各个字母在PF表中的座标的,后面会用到,而在座标是在填入PF表时得到的,所以我们在填充PF表时也要顺便填 字母表 后两行
    • 如何根据填入的序号求座标
      row = ablength/5 , line = ablength%5
      顺便要把row、line填进alp[1][]、alp[2][]中
  • 先使用密钥填充
    为了让密钥中的字母只在pf表中填充一次,所以我用alp第0行的0/1值来标记它们。我们约定0代表未用,1代表已用

    • 遍历密钥,检查 该字母的alp[0][] ,若为0,则用当前序号(abLength)求座标写入PF表,并在alp中记录下来,abLength++。
      若为1,跳过
  • Example:密钥第一个是e,那么 key[0] 强转 int型 就是 e的ASCLL值 ,再减去97就刚好是 alp中e的数组下标。此时alp[0][ (int)key[0] - 97] 等于0的话,说e不在pf表中,那就标记为1,并且根据 abLength 等于0求得e的座标为 row = 0/5, line = 0%5,把row、line记录到alp中并且把e写入到 PlayFair[0][0] 位置

  • NOTICE:I/J记得看成同一个

   for(int i=0 ; i<key.length() ; i++){//————————————————————遍历密钥     if(alp[0][((int)key[i]) - 97] == 0){ //——————————————(int)表示强转,列标参数表示密钥正在考察的那个字母的ASCLL值-97,其对应行标如果等于0,说明没重复,那就放进PF表,并且根据序号填座标入字母表,第0行写入1表示已用         if(key[i] == 'i'||key[i] == 'j'){  //—————————————这个判断显然就是为了让I/J一致,遇上I/J,要两个一起处理            alp[0][8]=alp[0][9]=1;            //注意里面的运算:用序号求座标            playfair[abLength/5][abLength%5] = key[i];            //添加alp在playfair里面的对应坐标            alp[1][8]=alp[1][9] = abLength/5;            alp[2][8]=alp[2][9] = abLength%5;            //弄完了序号加一            abLength++;        }        else{            alp[0][((int)key[i]) - 97] = 1;            playfair[abLength/5][abLength%5] = key[i];            alp[1][((int)key[i]) - 97] = abLength/5;            alp[2][((int)key[i]) - 97] = abLength%5;            abLength++;        }    }}
  • 用剩下的字母填充PF表与字母表

    for(int i = 0 ; i<26 ; i++){//————————————————————显然是for:a~z的遍历,i作为alp的列标    if (i == 8){ //———————————————————————————————i作为顺数第⑨个字母,在数组当然列标是⑧        if(alp[0][i] == 0){            char x =i+97;//———————————————————————强制转换            playfair[abLength/5][abLength%5] = x;            //i和i+1一起处理,也就是I/J一起处理            alp[1][i] = abLength/5;            alp[2][i] = abLength%5;             alp[1][i+1] = abLength/5;            alp[2][i+1] = abLength%5;            abLength++;            alp[0][i+1] = 1;        }    }    else if(alp[0][i] == 0 ){        char x =i+97;        playfair[abLength/5][abLength%5] = x;        alp[1][i] = abLength/5;        alp[2][i] = abLength%5;         abLength++;    } 

    }

此时PF表填充完毕,不信?你自己打印处理试试(就是I/J那个有点坑爹,加个判断即可)
为大家补充一份碉堡的程序框图(就这部分)
这里写图片描述
今天先到此为止,蟹蟹这里写图片描述

0 0
原创粉丝点击