W密码解密算法

news/2024/11/8 0:44:16/

栅栏密码

文章目录

条形栅栏密码

甚么是条形密码?看完加密方法就有直观的认识了

加密算法

在这里插入图片描述

这里将栅栏横着放用行line表示,有0到h-1共h行对应h个栅栏

设要加密的文本text,其字符下标从0开始

然后text[0]分配给0号栅栏

text[1]分配给1号栅栏

text[n]分配给n号栅栏

text[h-1]分配给h-1号栅栏

text[h]分配给1号栅栏

text[h+1]分配给2号栅栏

以此类推

也就是说已知当前行(栅栏号)为n则下一个栅栏应该是(n+1)%h

最终text的字符被这样轮流分配完毕,然后将各行按顺序合并得到加密密码

密码=line[0]+line[1]+line[2]+...+line[h-1]

算法设计:

bool isSpace(const char &c) {//忽略所有空格回车换行if (c == ' ' || c == '\n' || c == '\r')return true;return false;
}//栅栏密码算法
string fence_encrypt(const string &text, const int &cnt_fence) {//这里cnt_fence栅栏数就是前文的hstring fences[cnt_fence];int length = text.length();int now = 0;for (auto c : text) {if (isSpace(c))continue;fences[now] += c;//c放入当前栅栏now = (now + 1) % cnt_fence;//now指向下一个栅栏的位置}string encrypted;for (int i = 0; i < cnt_fence; i++) {encrypted += fences[i];}return encrypted;
}

解密算法

那拿来的放回哪里去,实际上解密算法和加密算法是完全相同的

string fence_decrypt(const string &encrypted, const int &cnt_fence) {string fences[cnt_fence];int length = encrypted.length();int now = 0;for (auto c : encrypted) {fences[now] += c;now = (now + 1) % cnt_fence;}string text;for (int i = 0; i < cnt_fence; i++) {text += fences[i];}return text;
}

W形栅栏密码

啥叫WWW加密呢?

区别于条形栅栏加密

条形栅栏加密:

将123456789A进行深度为3(栅栏数为3)的条形栅栏加密得到

line[0]=	1	4	7	A
line[1]=	2	5	8
line[2]=	3	6	9

密文为第0行+第1行+第2行=147A258369

注意实际编程的时候下标从0开始

考虑用W形将明文123456789A加密,深度为3

line[0]=	--1---5---9--
line[1]=	---2-4-6-8-A-
line[2]=	----3---7----

密文为第0行+第1行+第2行=1592468A37

也就是说,方向是这样的:

在这里插入图片描述

是个W形状,因此叫做WWW加密算法

WWW加密算法设计

我们需要加密的明文为text,text[0]表示明文的第0个字符

当深度为3的时候,

text[0]放在第0行

text[1]放在第1行

text[2]放在第2行

text[3]放在第1行

即text的字符会依次放在第0,1,2,1,0,1,2,1,0…行

规定一个方向指示director,下(+1),上(-1),表示下一个字符应该放在当前字符归属行一行还是一行

这个方向只能在达到最高处(第0行)或者达到最低处(第h-1行)转向,在中间的行(1到h-2行)只能遵守规定号的方向跳转,

这个过程神似穿针引线,从左上缝补到右下,然后到右上然后右下…

具体算法如下:

	int now=0;//当前行指针int director;//方向标识for (auto c : text) {if (now == 0)director = 1;//如果当前位置now在最高行则规定方向=1表示下一行应往低处(0->h-1方向)跳转else if (now == h - 1)director = -1;//如果当前位置now在最低行则规定方向=-1表示下一行应往高处(h-1->0)方向跳转//注意这里没有[2,h-2]这些行的条件判断,意味着这些行不会修改director方向,只会遵守方向lines[now] += c;//字符c加入当前行now = now + director;//根据director指示跳转到下一行}

完整代码:

string WWW_encrypt(const string &text, const int &h) {string lines[h];int length = text.length();int now = 0;int director;for (auto c : text) {if (now == 0)director = 1;else if (now == h - 1)director = -1;lines[now] += c;now = now + director;}string encrypted;for (int i = 0; i < h; i++) {//按照从0到h-1的顺序将各行拼接起来encrypted += lines[i];}return encrypted;
}

WWW解密算法推导

还是观察明文123456789A加密,深度为3的例子

line[0]=	--1---5---9--
line[1]=	---2-4-6-8-A-
line[2]=	----3---7----

密文为第0行+第1行+第2行=1592468A37

发现我们只需要还原出这三行的内容来然后借用刚才加密的思想,使用方向指示就可以还原出未加密的文本

关键在于怎么确定这几行每行的内容是啥

密文的前三个159归属第0行

然后2468A五个归属于第1行,

然后37两个归属于第2行,

貌似没有规律,不能确定哪几个归属于哪一行

但是可以确定的是,归属于同一行的字符一定挨着,并且最开始一定是归属于第一行的,然后是归属于第二行的,以此类推

确定字符对各行的分配规则cnt数组

定义"周期"

在这里插入图片描述

我们把红框称为"上周期",其中不含最低行(第h-1行)的字符
我们把黑框称为"下周期",其中不含最高行(第0行)的字符

两种周期的共同点是,每个周期里有h-1个字符,我们把"周期"作为"上周期"和"下周期"的笼统称呼

两种周期的差异点是上周期优先填充(或者说分配)高处行,方向是高到低( 0 → h − 1 0\rightarrow h-1 0h1),下周期有限填充低处行,方向是( h − 1 → 0 h-1\rightarrow 0 h10)

现在给定密文长度为n,已知采用深度为h的加密方法

那么完整的"周期"数为 c n t p h a s e = ⌊ n h − 1 ⌋ cnt_{phase}=\lfloor \frac{n}{h-1}\rfloor cntphase=h1n

如果 c n t p h a s e cnt_{phase} cntphase是一个偶数

则上下周期各占一半, c n t u p p e r p h a s e = c n t l o w e r p h a s e = 1 2 c n t p h a s e = 1 2 ⌊ n h − 1 ⌋ cnt_{upperphase}=cnt_{lowerphase}=\frac{1}{2}cnt_{phase}=\frac{1}{2}\lfloor \frac{n}{h-1}\rfloor cntupperphase=cntlowerphase=21cntphase=21h1n

到此,各行分到的字符数为

cnt[0]=cnt_upperphase;						//最高行只在所有上周期中每个上周期获得一个字符
cnt[1~h-2]=cnt_upperphase+cnt_lowerphase;		//中间行在所有上下周期中都可以每个周期获得一个字符
cnt[h-1]=cnt_lowerphase;					//最低行只在所有下周期中每个下周期获得一个字符

此时剩余没有分配的字符数量为 r e m a i n = n − c n t p h a s e × c h a r a c t e r s p e r p h a s e = n − c n t p h a s e × ( h − 1 ) = n − ⌊ n h − 1 ⌋ × ( h − 1 ) remain=n-cnt_{phase}\times characters\ per\ phase=n-cnt_{phase}\times (h-1)=n-\lfloor \frac{n}{h-1}\rfloor\times (h-1) remain=ncntphase×characters per phase=ncntphase×(h1)=nh1n×(h1)

如果remain=0说明所有字符恰好被完整个周期分配完毕,否则remain>0说明有剩余字符

这些剩余字符属于一个新的上周期,因此第0到remain-1行各自拿走一个

到此,各行分到的字符数为

cnt[0]=cnt_upperphase+1;						//最高行再获取一个
cnt[1~remain-1]=cnt_upperphase+cnt_lowerphase+1;		//前remain行都各自再获取一个
cnt[remain~h-1]=cnt_upperphase+cnt_lowerphase;			//此后各行保持不变
cnt[h-1]=cnt_lowerphase;					

分配完毕

如果 c n t p h a s e cnt_{phase} cntphase是一个奇数

如果 c n t p h a s e cnt_{phase} cntphase是一个奇数,则上周期比下周期多一个

c n t l o w e r p h a s e = ⌊ 1 2 c n t p h a s e ⌋ cnt_{lowerphase}=\lfloor\frac{1}{2}cnt_{phase}\rfloor cntlowerphase=21cntphase

c n t u p p e r p h a s e = ⌈ 1 2 c n t p h a s e ⌉ = c n t p h a s e − c n t l o w e r p h a s e cnt_{upperphase}=\lceil\frac{1}{2}cnt_{phase}\rceil=cnt_{phase}-cnt_{lowerphase} cntupperphase=21cntphase=cntphasecntlowerphase,用计算机取上整数比较麻烦

显然 c n t p h a s e cnt_{phase} cntphase是一个奇数时的计算方法同样适用于偶数
{ c n t p h a s e = ⌊ n h − 1 ⌋ c n t l o w e r p h a s e = ⌊ 1 2 c n t p h a s e ⌋ c n t u p p e r p h a s e = ⌈ 1 2 c n t p h a s e ⌉ = c n t p h a s e − c n t l o w e r p h a s e \begin{cases} cnt_{phase}=\lfloor \frac{n}{h-1}\rfloor\\ cnt_{lowerphase}=\lfloor\frac{1}{2}cnt_{phase}\rfloor\\ cnt_{upperphase}=\lceil\frac{1}{2}cnt_{phase}\rceil=cnt_{phase}-cnt_{lowerphase}\end{cases} cntphase=h1ncntlowerphase=21cntphasecntupperphase=21cntphase=cntphasecntlowerphase
到此,各行分到的字符数为

cnt[0]=cnt_upperphase;						//最高行只在所有上周期中每个上周期获得一个字符
cnt[1~h-2]=cnt_upperphase+cnt_lowerphase;		//中间行在所有上下周期中都可以每个周期获得一个字符
cnt[h-1]=cnt_lowerphase;					//最低行只在所有下周期中每个下周期获得一个字符

此时剩余没有分配的字符数量为 r e m a i n = n − c n t p h a s e × c h a r a c t e r s p e r p h a s e = n − c n t p h a s e × ( h − 1 ) = n − ⌊ n h − 1 ⌋ × ( h − 1 ) remain=n-cnt_{phase}\times characters\ per\ phase=n-cnt_{phase}\times (h-1)=n-\lfloor \frac{n}{h-1}\rfloor\times (h-1) remain=ncntphase×characters per phase=ncntphase×(h1)=nh1n×(h1)

如果remain=0说明所有字符恰好被完整个周期分配完毕,否则remain>0说明有剩余字符

这些剩余字符属于一个新的下周期,因此第h-1到h-2+remain行各自拿走一个

到此各行分到的字符数为

cnt[0]=cnt_upperphase;						
cnt[1~h-remain-1]=cnt_upperphase+cnt_lowerphase		//h-remain-1往上的行不变
cnt[h-remain~h-1]=cnt_upperphase+cnt_lowerphase+1;	//[h-1,h-remain]行均从新的下周期中获得一个字符
cnt[h-1]=cnt_lowerphase+1;					//最低行在新的下周期中获得一个字符
奇数和偶数时的区别

由于奇数时计算周期的公式同样适用于偶数
{ c n t p h a s e = ⌊ n h − 1 ⌋ c n t l o w e r p h a s e = ⌊ 1 2 c n t p h a s e ⌋ c n t u p p e r p h a s e = ⌈ 1 2 c n t p h a s e ⌉ = c n t p h a s e − c n t l o w e r p h a s e \begin{cases} cnt_{phase}=\lfloor \frac{n}{h-1}\rfloor\\ cnt_{lowerphase}=\lfloor\frac{1}{2}cnt_{phase}\rfloor\\ cnt_{upperphase}=\lceil\frac{1}{2}cnt_{phase}\rceil=cnt_{phase}-cnt_{lowerphase}\end{cases} cntphase=h1ncntlowerphase=21cntphasecntupperphase=21cntphase=cntphasecntlowerphase
因此在两种情况,只有在处理最后剩下的不完整的新周期时,才有不同,

不同的原因是上下周期分配字符的方向和起点不同

总结成算法O(n)
vector<int> allocate(const int &n, const int &h) { //n个字符,加密深度为hvector<int> cnt(h);int cnt_phase = n / (h - 1);int cnt_lowerphase = cnt_phase / 2;int cnt_upperphase = cnt_phase - cnt_lowerphase;cnt[0] = cnt_upperphase;cnt[h - 1] = cnt_lowerphase;for (int i = 1; i <= h - 2; i++) {cnt[i] = cnt_phase;}int remain = n - cnt_phase * (h - 1);if (remain == 0)return cnt;if (cnt_phase % 2 == 0) {for (int i = 0; i <= remain - 1; ++i)++cnt[i];} else {for (int i = h - 1; i >= h - remain; --i)++cnt[i];}return cnt;
}

以1592468A37为例验证:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<int> allocate(const int &n, const int &h);//详见上文
int main() {string text = "1592468A37";vector<int> cnt = allocate(text.length(), 3);for (auto i : cnt)cout << i << " ";return 0;
}

结果

3 5 2

是正确的

另一种更简单的cnt数组的计算方式

注意到

vector<int> allocate(const int &n, const int &h);

这个函数并没有获取密码的具体字符信息,而是只给定了密码长度和加密深度,就可以求出一个cnt数组

也就是说,cnt数组和密码是什么字符没有关系

那么我们岂不是可以正着来,直接用加密算法求得cnt数组?

给加密算法encrypt函数传入一个n位长的任意字符串可以求得各行分配到的字符,自然也就求得了各行应该分配的字符个数

也就是说,不管是对123456789还是987654321加密,其排列出的W的形状是相同的

我们现有的加密算法:

string WWW_encrypt(const string &text, const int &h) {string lines[h];int length = text.length();int now = 0;int director;//方向指示标记for (auto c : text) {if (now == 0)director = 1;else if (now == h - 1)director = -1;lines[now] += c;now = now + director;}string encrypted;for (int i = 0; i < h; i++) {encrypted += lines[i];}return encrypted;
}

只需要对其进行一些改造

vector<int> allocate(const int &n, const int &h) {vector<int> cnt(h);int director;//方向指示标记int now = 0;for (int i = 1; i <= n; i++) {if (now == 0)director = 1;else if (now == h - 1)director = -1;++cnt[now];now = now + director;}return cnt;
}

于是同样用 O ( n ) O(n) O(n)甚至更实际更优于前一种的方法出现了

根据cnt数组指示,从密码上连续截取相应数量的字符

根据刚才我们推测得到的算法,

第0行应当截取密码的从0开始,长度为cnt[0]这一段字串encrypted.substr(0,cnt[0]);

第1行应从第0行截取剩下的开始截取,长度为cnt[1]这一字串encrypted.substr(cnt[0],cnt[1])

以此类推,第n行应该截取encrypted.substr(cnt[0]+cnt[1]+....+cnt[n-1],cnt[n])

line[0]=encrypted.substr(0,cnt[0]);
line[n]=encrypted.substr(length,cnt[n]);
length=sum(cnt[0],cnt[1],...,cnt[n-1]);

总结算法:

vector<string> getLines(const string &encrypted, const int &h) {vector<string> line(h);vector<int> cnt = allocate(encrypted.length(), h);line[0] = encrypted.substr(0, cnt[0]);int length = 0;//累加前面的字串长度for (int i = 1; i <= h - 1; i++) {length += cnt[i - 1];//累加line[i] = encrypted.substr(length, cnt[i]);}return line;
}

方向指针解密

现在各行都已经求出来了,得到了我们想象中的这样的结构:

line[0]=	--1---5---9--
line[1]=	---2-4-6-8-A-
line[2]=	----3---7----

实际上是这样的:

line[0]=1,5,9
line[1]=2,4,6,8,A
line[2]=3,7

现在又到了穿针引线的时间,director指示跳转的方向,每行还需要维护一个标记,记录当前行应该取哪个字符了,方便下一次取

什么意思呢?具体来说

注意下标,排名都是从0开始

初始时各行的标记mark[i]=0都是0,意思是,如果要取,肯定从首个(第0个)字符开始

从第0行开始取direction置1,取第0个元素1,++mark[0],意思是第一行的首个元素已经取出,如果下次跳转到第一行取数时应该取下一个元素

按照direction=1下跳到第2行,取第0个元素2,++mark[1],意思时第二行的首个元素已经取出,如果下次跳转到第二行取数时应该取下一元素

按照direction=1下跳到第2行,置direction=-1,取第0个元素3,++mark[2],意思时第二行的首个元素已经取出,如果下次跳转到第二行取数时应该取下一元素

按照direction=-1上跳到第1行,取第1个元素4,++mark[1],意思是第二行的第二个元素已经取出,如果下次跳转到第二行取数时应该取下一元素

string decrypt(const string &encrypted, const int &h) {vector<string>line = getLines(encrypted, h);int length = encrypted.length();int director = 0;int now = 0;int mark[h];for (int i = 0; i < h; i++) {mark[i] = 0;//mark数组置0}string decrypted;for (int i = 0; i < length; i++) {if (now == 0)director = 1;else if (now == h - 1)director = -1;decrypted += line[now][mark[now]];++mark[now];//当前行标记后移now += director;//now按照director指示跳转}return decrypted;
}

完整算法

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;vector<int> allocate(const int &n, const int &h) { //n个字符,加密深度为hvector<int> cnt(h);int cnt_phase = n / (h - 1);int cnt_lowerphase = cnt_phase / 2;int cnt_upperphase = cnt_phase - cnt_lowerphase;cnt[0] = cnt_upperphase;cnt[h - 1] = cnt_lowerphase;for (int i = 1; i <= h - 2; i++) {cnt[i] = cnt_phase;}int remain = n - cnt_phase * (h - 1);if (remain == 0)return cnt;if (cnt_phase % 2 == 0) {for (int i = 0; i <= remain - 1; ++i)++cnt[i];} else {for (int i = h - 1; i >= h - remain; --i)++cnt[i];}return cnt;}vector<string> getLines(const string &encrypted, const int &h) {vector<string> line(h);vector<int> cnt = allocate(encrypted.length(), h);line[0] = encrypted.substr(0, cnt[0]);int length = 0;for (int i = 1; i <= h - 1; i++) {length += cnt[i - 1];line[i] = encrypted.substr(length, cnt[i]);}return line;
}string decrypt(const string &encrypted, const int &h) {vector<string>line = getLines(encrypted, h);int length = encrypted.length();int director = 0;int now = 0;int mark[h];for (int i = 0; i < h; i++) {mark[i] = 0;}string decrypted;for (int i = 0; i < length; i++) {if (now == 0)director = 1;else if (now == h - 1)director = -1;decrypted += line[now][mark[now]];++mark[now];now += director;}return decrypted;
}int main() {string text = "1592468A37";cout << decrypt(text, 3);return 0;
}

http://www.ppmy.cn/news/839571.html

相关文章

C语言实现凯撒密码加解密

凯撒密码 加密即把a-z或A-Z的字母向后移动n个字符实现加密&#xff0c;若n3的话&#xff0c;a对应d&#xff0c;z对应c&#xff0c;如此循环&#xff1b;解密刚好和加密相反&#xff0c;加密向后移动的话解密就向前移动。 加密的C代码如下&#xff1a; #include <stdio.h&…

ctf密码学加解密

base编码&#xff1a; 在字符串后看到&#xff1a;&#xff0c;很可能是base64编码 编码格式base16大写字母A-F和0-9base32大写字母A-Z和数字2-7base58大小写字母和数字去除0(零)和O(大写o)、I(大写i)和l(小写L)base64大小写字母和数字0-9以及"" "/"base…

凯撒密码加解密实现(python)

Caesar&#xff08;凯撒密码&#xff09; 原理 凯撒密码&#xff08;Caesar&#xff09;加密时会将明文中的 每个字母 都按照其在字母表中的顺序向后&#xff08;或向前&#xff09;移动固定数目&#xff08;循环移动&#xff09;作为密文。例如&#xff0c;当偏移量是左移 3…

维吉尼亚密码加解密

西安电子科技大学Python程序设计上机实验——维吉尼亚密码加解密 维吉尼亚密码是使用一系列凯撒密码组成密码字母表的加密算法&#xff0c;属于多表密码的一种简单形式。为了生成密码&#xff0c;需要使用表格法。这一表格包括了 26 行字母表&#xff0c;每一行都由前一行向左…

加密解密

//加密解密 public class GF_Encrypt { /// <summary> /// DES加密 /// </summary> /// <param name"pToEncrypt">加密字符串</param> /// <param name"sKey">密钥</param> /// <returns></returns> publ…

仿射密码解密(Affine Cipher)

仿射密码是一种表单代换密码&#xff0c;字母表的每个字母相应的值使用一个简单的数学函数对应一个数值&#xff0c;再把对应数值转换成字母。 ABCDEFGHIJKLMNOPQRSTUVWXYZ012345678910111213141516171819202122232425 加密函数&#xff1a;E(x) (ax b) (mod m)&#xff0c…

古典密码----仿射密码加解密

理论部分 仿射密码是移位密码的一个推广&#xff0c;其加密过程不仅包含移位操作&#xff0c;而且使用了乘法运算。与移位密码相同&#xff0c;仿射密码的明文空间M和密文空间C均为Z26&#xff0c;因此&#xff0c;在使用仿射密码体制对英文消息进行加密之前&#xff0c;需要在…

密码加解密java语言实现

目录 前言 1.密码加密的主要方式 2.代码的实现过程 3.整体代码 前言 随着技术的发展&#xff0c;密码加密技术已经越来越普遍越来越多样化&#xff0c;在我们生活中常见的加密算法包括了DES加密算法,AES加密算法,RSA加密算法,MD5加密算法等等。密码加密在我们国家社会生活中…