凯撒(Caesar)密码——JavaScript实现

什么是凯撒密码

凯撒密码最早由古罗马军事统帅盖乌斯·尤利乌斯·凯撒在军队中用来传递加密信息,故称凯撒密码。这是一种位移加密方式,只对26个字母进行位移替换加密,规则简单,容易破解。

若将明文字母表向后移动3位:

明文 X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
密文 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

则A变成了D,B变成了E……,Z变成了C。

字母表最多可以移动25位。凯撒密码的明文字母表向后或向前移动都是可以的,通常表述为向后移动,如果要向前移动1位,则等同于向后移动25位,位移选择为25即可。

加密解密方式

纵观常用的在线加解密网站,会发现通常有两种实现:

  1. 不论大小写,均统一成其中一种进行加解密输出,key值限定为非负整数;
  2. 大写字母若发生进位,变成小写;小写字母若发生进位,变成大写。

关键思路

假设 f(x) 为当前字母在字母表[a-z]中的位置,key为偏移量,z为字母表中z的位置,那么:

加密过程
key为非负整数,a 偏移到z 后,会再从a 继续开始偏移,故循环周期为字母表长度26
key = key Mod 26;
f(x) = f(x) + key > z ? f(x) + key -26 : f(x) + key

解密过程
key为非负整数,z 回退到a 后,会再从z继续回退偏移,故循环周期为字母表长度26
key = key Mod 26;
f(x) = f(x) - key < a ? f(x) - key + 26 : f(x) - key

方案1:忽略大小写,key值限定为非负整数

// 加密,需要明文(plaintext)和密钥(key)作为参数
function encryptCaesar(plaintext, key) {
	// 校验key值有效性——非负整数
	if (!(/^0|[1-9]\d*$/.test(key))) {
		return new Error('位移量请输入非负整数');
	}
	// 字符串分解成数组
	let arr = plaintext.toString().split('');
	let charCodeOf_a = 'a'.charCodeAt();
	let charCodeOf_z = 'z'.charCodeAt();
	key = key % 26;

	for (let i = 0; i < arr.length; i ++) {
		// 当前字符小写字母的ASCII码值
		let currentChar = arr[i].toLowerCase().charCodeAt();
		if (currentChar >= charCodeOf_a && currentChar <= charCodeOf_z) {
			// 偏移量,判断是否加密后回到[a-z]头部
			let currentOffset = currentChar + key - charCodeOf_z;
			if (currentOffset > 0) {
				currentChar -= 26;
			}
			currentChar += key;
		}
		arr[i] = String.fromCharCode(currentChar);
	}
	return arr.join('');
}
encryptCaesar('Didi Family', 10); // "nsns pkwsvi"

// 解密,需要密文(ciphertext)和密钥(key)作为参数
function decryptCaesar(ciphertext, key) {
	// 校验key值有效性——非负整数
	if (!(/^0|[1-9]\d*$/.test(key))) {
		return new Error('位移量请输入非负整数');
	}
	// 字符串分解成数组
	let arr = ciphertext.toString().split('');
	let charCodeOf_a = 'a'.charCodeAt();
	let charCodeOf_z = 'z'.charCodeAt();
	key = key % 26;

	for (let i = 0; i < arr.length; i ++) {
		let currentChar = arr[i].toLowerCase().charCodeAt();
		if (currentChar >= charCodeOf_a && currentChar <= charCodeOf_z) {
			// 偏移量,判断是否解密后回到[a-z]尾部
			let currentOffset = currentChar - charCodeOf_a - key;
			if (currentOffset < 0) {
				currentChar += 26;
			}
			currentChar -= key;
		}
		arr[i] = String.fromCharCode(currentChar);
	}
	return arr.join('');
}
decryptCaesar('"nsns pkwsvi"', 10) // "didi family"

以上代码分别实现了不考虑大小写的情况下加密和解密的过程,当涉及到大小写变换的时候,情况变得稍微复杂些:

加密过程
key为非负整数,a偏移到z后,变为[A-Z]循环,需要改变字母大小写
cycle = (f(x) + key) / 26
当cycle为奇数时,需要变换大小写;cycle为偶数时,可以不用变换大小写
f(x) = (f(x) + key) % 26
if (cycle % 2) f(x) = f(x).toUpperCase();

解密过程
key为非负整数,Z回退到A后,变为[z-a]循环,需要改变字母大小写
cycle = (f(x) - key) / 26
当cycle为偶数时,需要变换大小写;cycle为奇数时,不用变换大小写
f(x) = (f(x) - key) % 26
if (!(cycle % 2)) f(x) = f(x).toLowerCase();

方案2:区分大小写,key为非负整数

// 加密,需要明文和密钥
function encryptCaesar(plaintext, key) {
	if (!(/^0|[1-9]\d$/.test(key))) {
		return new Error('请输入非负整数');
	}
	let arr = plaintext.toString().split('');
	let charCodeOf_a = 'a'.charCodeAt(); // 小写字母a的ASCII码值
	let charCodeOf_A = 'A'.charCodeAt(); // 大写字母A的ASCII码值

	for (let i = 0; i < arr.length; i ++) {
		let currentChar = arr[i].charCodeAt(); // 当前字母的ASCII码值
		let cycle = 0; // 奇数改变大小写,偶数不变,默认不变
		if (/^[a-z]+$/.test(arr[i])) {
		    let offset_a = currentChar - charCodeOf_a + key; // 算法中的f(x) + key
		    cycle = parseInt(offset_a / 26);
			currentChar = charCodeOf_a + offset_a % 26;
			let temp = String.fromCharCode(currentChar);
			arr[i] = cycle % 2 ? temp.toUpperCase() : temp;
		} else if (/^[A-Z]+$/.test(arr[i])) {
		    let offset_A = currentChar - charCodeOf_A + key;
		    cycle = parseInt(offset_A / 26);
			currentChar = charCodeOf_A + (offset_A) % 26;
			let temp = String.fromCharCode(currentChar);
            arr[i] = cycle % 2 ? temp.toLowerCase() : temp;
		}
	}
	return arr.join('');
}

// 解密,需要密文和密钥
function decryptCaesar(ciphertext, key) {
	if (!(/^0|[1-9]\d$/.test(key))) {
		return new Error('请输入非负整数');
	}
	let arr = ciphertext.toString().split('');
	let charCodeOf_a = 'a'.charCodeAt();
	let charCodeOf_A = 'A'.charCodeAt();

	for (let i = 0; i < arr.length; i ++) {
		let currentChar = arr[i].charCodeAt();
		let cycle = 1;
		if (/^[a-z]+$/.test(arr[i])) {
		    let offset_a = currentChar - charCodeOf_a - key;
		    // 注意offset_a为正时不需要变换大小写
		    cycle = offset_a < 0 ? parseInt(offset_a / 26) : 1;
            if (charCodeOf_a + offset_a < charCodeOf_a) {
                offset_a = offset_a % 26 + 26;
            }
            currentChar = charCodeOf_a + offset_a;
			let temp = String.fromCharCode(currentChar);
			arr[i] = cycle % 2 ? temp : temp.toUpperCase();
		} else if (/^[A-Z]+$/.test(arr[i])) {
		    let offset_A = currentChar - charCodeOf_A - key;
		    // 注意offset_A为正时不需要变换大小写
		    cycle = offset_A < 0 ? parseInt(offset_A / 26) : 1;
		    if (charCodeOf_A + offset_A < charCodeOf_A) {
		        offset_A = offset_A % 26 + 26;
		    }
			currentChar = charCodeOf_A + offset_A;
			let temp = String.fromCharCode(currentChar);
            arr[i] = cycle % 2 ? temp : temp.toLowerCase();
		}
	}
	return arr.join('');
}
decryptCaesar("Nsns PkwsvI", 10) // "Didi Family"
decryptCaesar("RCzsrsr XIFLG", 17) // "Alibaba Group"

猜你喜欢

转载自blog.csdn.net/Small_Wchen/article/details/88753564