一般情况下所说的“加密”,是指对一段数据进行特定的处理后,使其不被会人或计算机识别出原有数据的过程;“解密”则是与之相反的过程,即将加密处理后的数据还原成原始数据的过程。加密算法都有一定的“加密强度”,加密强度越高,代表着这种加密算法被破译的可能性越小,或者说破译这种加密算法的代价越高。一般研制加密算法时,并不要求该算法一定不可以会被破译,而是达到一定的加密强度目标就可以。例如,如果只是朋友之间传送几句无伤大雅但又不是很愿意让外人看到的话,用强度很低的加密算法就可以了;如果涉及商业机密等高敏感性的数据,则需要加密强度很高的算法。
如同我们前面所说,计算机中一般最小的数据单位是字节,除了很特殊的情况,一般很少用二进制位(也叫比特,即bit)来做最小数据单位。Go语言中,绝大多数类型的数据都可以转换为字节切片([]byte类型),因此,如果一种加密算法和对应的解密算法能够对字节切片进行加密解密,那么它也就可以对所有的数据类型进行加密解密。
本节将介绍一个自定义的、非常简单的,加密强度也比较低的加密算法和其对应的解密算法,主要用于演示加密、解密算法的基本实现方式。
这个算法的描述如下:
-> 加密时,首先要确定一个密码,例如“test”,转换成字节切片将是[]byte{0x74, 0x65, 0x73,
0x74};
-> 假设要加密的一段文本字符串是“This is an example.”,也转换成字节切片是[]byte{0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e};
-> 加密的过程中,先将原文字符串的第一个字节与密码的第一个字节相加,得到加密后的第一个字节。注意如果两个字节相加超过255,将会发生“溢出”的情况,即该数据类型无法容纳计算后的结果,例如一个字节最大能够表示的整数是255,而如果代表原文和密码的两个字节分别是十进制的200和90,则相加结果为290,超过了255,此时该结果会被去掉255,变成35。溢出对我们的计算并不会有影响,因为解密时如果把35减去90,这时候会发生“负向”的溢出,得到-55,由于字节必须是0至255范围之间的整数,所以最终结果会再加上255而得到200,这正好是原数据的值。
-> 然后依次将原文的第二个字节与密码的第二个字节相加,如此继续下去,直到原文的第5个字节时,密码已经没有多余的字节,此时就再从头加上密码的第1个字节,然后原文的第6个字节加上密码的第2个字节,……,如此循环往复直到所有字节加密完毕;
-> 为了提高一定的加密强度,我们再在之前每一次相加中再多加一个循环变量的当前值,即第一次相加(原文的第一个字节与密码的第一个字节相加)再多加0,第二次相加多加1,如此一直到加密完原文最后一个字节即得到最终加密后的密文。
-> 由于密文是由字节加法而来,各个字节的值不一定像原文那样是可显示字符,因此密文一般只能以字节切片的形式存在,如果保存成文件也是二进制文件。
-> 解密过程与加密过程完全相反,即把密文的每个字节与密码的每个字节循环相减,再多减一个循环变量的值,这样就会还原出原文的所有字节。
-> 这种加密解密算法,显然需要加密解密的双方都知道密码才可以进行正确的加密解密。
下面是实现该算法的代码:
package main
import (
t "tools"
)
// encryptBytes 是用密码codeA来加密原文dataA的函数
func encryptBytes(dataA []byte, codeA []byte) []byte {
//获取原始数据的长度
dataLenT := len(dataA)
//获取密码的长度
codeLenT := len(codeA)
//创建放置密文的缓冲区,其大小与原文的字节长度应该是一样的
encBufT := make([]byte, dataLenT)
//循环进行加密
for i := 0; i < dataLenT; i++ {
// codeA[i%codeLenT]可以保证循环取到合理索引范围内的密码字节
encBufT[i] = dataA[i] + codeA[i%codeLenT]+ byte(i)
}
return encBufT
}
// decryptBytes 是用密码codeA来解密密文dataA的函数
func decryptBytes(dataA []byte, codeA []byte) []byte {
//获取密文数据的长度
dataLenT := len(dataA)
//获取密码的长度
codeLenT := len(codeA)
//创建放置还原的原文的缓冲区,其大小与密文的字节长度应该是一样的
decBufT := make([]byte, dataLenT)
//循环进行解密
for i := 0; i < dataLenT; i++ {
// codeA[i%codeLenT]可以保证循环取到合理索引范围内的密码字节
decBufT[i] = dataA[i] - codeA[i%codeLenT]- byte(i)
}
return decBufT
}
func main() {
originText := "This is an example."
codeT := "test"
t.Printfln("原文是:%#v", originText)
t.Printfln("密码是:%#v", []byte(codeT))
encBytes := encryptBytes([]byte(originText),[]byte(codeT))
t.Printfln("加密后的字节切片是:%#v", encBytes)
decBytes := decryptBytes(encBytes,[]byte(codeT))
t.Printfln("解密后的字节切片是%#v", decBytes)
t.Printfln("解密后还原的原文是%#v", string(decBytes))
}
代码 14‑1crypto1/crypto1.go
代码14‑1忠实地实现了前述的加密解密算法,代码很简单,其中也有足够的注释,运行后的结果如下:
原文是:"This is an example."
密码是:[]byte{0x74, 0x65, 0x73, 0x74}
加密后的字节切片是:[]byte{0xc8, 0xce, 0xde, 0xea, 0x98, 0xd3, 0xec, 0x9b, 0xdd, 0xdc, 0x9d, 0xe4, 0xf8, 0xd3, 0xee, 0xf3, 0xf0, 0xdb, 0xb3}
解密后的字节切片是[]byte{0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e}
解密后还原的原文是"This is an example."
可以看到,原文经过加密和解密两个相反的过程之后,再次被还原如初,说明整个加密和解密过程都执行正确,算法也无误。
这个算法和本代码实现的加密解密函数实际上可以用于任何类型数据的加密,加密时只要将该种类型的数据转换为字节切片,而解密时做反向的转换就可以了(例如使用gob包来进行序列化和反序列化)。加密强度虽然不高,但对于非敏感数据的简单加密需求一般也够用了。
如果所需加密/解密的数据量很大,需要使用流式的方法,参照文件拷贝的例子结合本节的加解密方法可以很容易地实现。
另外,在本节代码中加上文件的读取和写入操作,完全可以实现对任何文件的加密解密,由于比较简单,在此不再深入举例。