还记得初二的暑假,班上来了一个新同学。他住在我家对面的楼里,所以我们一起上学放学,很快就成了最好的朋友。我们决定发明一种神秘的交流方式,任何人都不可能猜出它的真正含义。我们首先想到的是汉语拼音,但显然将一句话变成汉语拼音是不够的,所以我们将26个英文字母以编号的形式从低到高排列,得到了一个简单的密码本:之后用这本密码本改造《我们都是好朋友》,得到如下结果:小时候玩这个游戏百听不厌,觉得很有趣。上了大学之后,有幸听了卢开诚教授的讲座《计算机密码学》,才知道我们小时候玩的游戏远不能叫加密。那么什么是加密?什么是加密?将字符串123456转换为base64后,我们得到MTIzNDU2。有人说是base64加密。字符串123456经过md5转换后得到E10ADC3949BA59ABBE56E057F20F883E。有人说这是md5加密。严格来说,无论是base64,还是md5,甚至更复杂的sha256,都不能称为加密。总之,没有密钥的算法不能称为加密。编码(Encoding)是将字符集中的字符编码成指定集合中的对象(例如:位模式、自然数序列、8位字节或电脉冲),从而使文本在计算机中存储并通过通信网络传输,常见的例子包括将拉丁字母表编码成摩尔斯电码和ASCII。base64只是一种编码。哈希是计算机科学中的一种数据处理方法。通过特定的函数/算法(称为散列函数/算法),将要检索的项和用于检索的索引(称为散列,或散列值))生成易于查找的数据结构(称为散列表).哈希算法通常用于保护存储在数据库中的密码字符串。由于哈希算法计算出的哈希值是不可逆的(不能逆转回原来的值),因此可以有效的保护密码。常用的哈希算法有md5、sha1、sha256等。加密就是将明文信息变成不可读的密文内容,使其不可读的过程。只有具有解密方法的对象才能通过解密过程将密文恢复为正常可读的内容。加密分为对称加密和非对称加密。常用的对称加密算法有DES、AES等,非对称加密算法有RSA、椭圆曲线算法等。在经典的加密算法中,加密算法和密钥是不能公开的。一旦泄露,就有被破解的风险。我们可以使用词频计算等方法来获取明文。1972年,美国IBM公司开发的DES算法(DataEncryptionStandard)是人类历史上第一个公开加密算法但不公开公钥的加密方法,后来成为美军和政府机构。2002年升级为AES算法(高级加密标准)。今天开始学习AES的加解密。准备工具通常情况下,加密和解密只需要在服务器端完成,网上大部分教程和示例代码也是如此,但是在某些特殊情况下,需要用一种语言加密,解密时使用另一种语言一种语言,最好有一个中立公正的第三方结果集来验证你的加密结果。否则万一出了问题,你也不知道是加密算法错了,还是解密算法错了。我们对此负责有惨痛的教训,特别是如果在一个公司里,前端用js语言写加密,后端用java语言、php语言或者go语言写解密,那么双方需要有这样一个客观公正的平台,否则难免会陷入相互指责永无休止的境地。前端说没有错,但是后端解密错了。事实上,双方都是对密码学一知半解的菜鸟,在这种情况下浪费了更多的时间。在线AES加密解密就是这样一个工具网站。您可以在上面验证您的加密结果。如果你加密后得到的结果和它的结果完全一样,说明你的加密算法没有问题。否则,您可以对其进行调整,直到完全符合其结果。反之亦然,如果它可以从密文中解密,但你的代码不能,那么一定是你的算法有问题,而不是数据。我们先来加密这个网站上的一个简单的字符串123456。下面我们一一解释网站上的所有选项:AES加密模式:这里我们选择ECB(eeccblock)模式。这是所有AES模式中最简单也是最不推荐的模式,因为它的固定明文对应固定密文,很容易被破解。但由于这是一个练习,让我们从最简单的开始。padding:这里我们选择pkcs标准的pkcs7padding。数据块:我们选择128位,因为java端的解密算法目前只支持AES128,所以我们先从128位入手。key:因为我们选择了128位的数据块,所以这里我们使用128/8=16字节来处理,我们简单的填16个0,其实你也可以填任意字符,比如abcdefg1234567ab或者Others,只要因为它是16个字节。理论上不是16个字节也可以作为key,优秀的算法会自动完成,但是为了简单起见,我们先填16个0。偏移量:留空。因为是ECB模式,所以不需要ivoffset。输出:我们选择base64编码方式。字符集:这里因为我们只加密英文字母和阿拉伯数字,所以utf-8和gb2312的选择是一样的。好了,现在我们知道如果设置了以上选项后的代码加密123456,应该输出DoxDHHOjfol/2WxpaXAXgQ==,如果不是这个结果,那就是加密这边的问题了。AES-ECB1。AES-ECB的Javascript加密为了完成AES加密,我们不需要自己写一个AES算法,也不需要重新造轮子。但是如何选择js的加密库是一个很有意思的挑战。我们尝试了很多方法。一开始我们尝试了aes-js库,但是不支持RSA算法。然后我们看到浏览器自带的加密库WebCryptoAPI,原生支持AES和RSA,但是其RSA实现与Java不兼容。最终我们选择了Forge库,它天然支持AES的各种子集,其RSA也可以与Java完美配合。使用forge写的js代码实现AES-ECB加密的代码如下:constcipher=forge.cipher.createCipher('AES-ECB','这里是16字节的密钥');cipher.start();密码。update(forge.util.createBuffer('这里是明文'));cipher.finish();constresult=forge.util.encode64(cipher.output.getBytes())forge的AES默认是pkcs7padding,所以没有特别说明需要设置。运行后你会得到正确的加密结果。2.AES-ECB的Java解密接下来我们看看Java端解密代码的写法:"这里是一个16字节的密钥".getBytes(),"AES"));Stringplaintext=newString(cipher.doFinal(Base64.getDecoder().decode("这里是明文".getBytes())),"UTF-8");System.out.println(plaintext);}catch(Exceptione){System.out.println("解密错误:"+e.toString());}注意我们这里使用的是PKCS5Padding,是加密的上面那个时候不是用pkcs7padding吗?为什么这里变成了5?我们先来了解一下什么是pkcs。pkcs的全称是PublicKeyCryptographyStandards(公钥密码学标准),是由RSA实验室制定的一系列公钥密码学标准。比较出名的有pkcs1、pkcs5、pkcs7、pkcs8,分别管理的是不同的内容。这里我们只是用它来填充,所以我们只关注pkcs5和pkcs7。那么pkcs5和pkcs7有什么区别呢?其实他们两个算法在填充方面是一样的。pkcs5是pkcs7的一个子集。区别在于pkcs5固定为8个字节,而pkcs7可以在1到255个任意字节之间。但是用在AES算法中,因为AES标准规定块大小必须是16字节或者24字节或者32字节,不可能使用8字节的pkcs5,所以AES算法只能用pkcs7来填充。但是由于早期java工程师的一个命名错误,他们将AES填充算法的名称定为pkcs5,但实际实现是pkcs7,所以我们在java端开发解密时需要使用pkcs5。AES-CBC说完不安全的AES-ECB,我们来做一个相对安全的AES-CBC模式。1、AES-CBC的Javascript加密直接上传代码:constcipher=forge.cipher.createCipher('AES-CBC','这里是一个16字节的密钥');cipher.start({iv:'这里是一个16字节的密钥Offset'});cipher.update(forge.util.createBuffer('这里是明文'));密码.finish();constresult=forge.util.encode64(cipher.output.getBytes());跟上面的AES-ECB类似,唯一不同的是在start函数中定义了一个iv。2、AES-CBC的Java解密下面是Java代码:try{Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE,newSecretKeySpec("这里是16字节的密钥".getBytes(),"AES"),newIvParameterSpec("这里是16字节偏移".getBytes()));Stringplaintext=newString(cipher.doFinal(Base64.getDecoder().decode("这里是明文".getBytes())),"UTF-8");System.out.println(plaintext);}catch(Exceptione){System.out.println("Decryptionerror:"+e.toString());}是一样的,使用的时候和上面的模式几乎一模一样AES-ECB,但是增加了一个IvParameterSpec来生成iv,在cipher.init中增加了一个iv参数,只是完全一样,所以我们实现了一个SimpleCBC模式。RSA,但是上面两种方式显然是非常不安全的,因为我们直接把加密密钥和iv参数暴露给了前端,所以我们需要一种更安全的加密方式——RSA。因为RSA是非对称加密,所以即使我们将用于加密的公钥完全暴露给前端,也不用担心。即使别人截获了我们的密文,也无法解密我们的明文,因为他们没有解密密钥。1、生成密钥对使用RSA加密,首先我们需要生成公钥和私钥,直接执行命令ssh-keygen即可。它会询问我们保存密钥文件的文件夹。注意找一个单独的文件夹存放,不要在默认文件夹中,否则你日常的ssh公钥和私钥会被覆盖。拿到公钥文件后,由于公钥文件是rfc4716格式,而我们的forge库需要pkcs1格式的公钥,这里需要将其转为pem格式(即pkcs1格式):ssh-keygen-f公钥文件名-mpem-e2。RSA的Javascript加密得到pem格式的公钥后,再看js代码:forge.util.encode64(forge.pki.publicKeyFromPem('-----BEGINRSAPUBLICKEY-----MIIBCfdsafasfasfafsdaafdsaAB------ENDRSAPUBLICKEY-----').encrypt('这里是明文','RSA-OAEP',{md:forge.md.sha256.create(),mgf1:{md:forge.md.sha1.create()}});一句话就完成了整个加密过程,这就是forge的威力3.RSA的Java解密接下来我们来看解密。对于私钥,因为Java只支持PKCS8,而我们用ssh-keygen生成的私钥是pkcs1,所以我们需要使用如下命令将pkcs1的私钥转换为pkcs8的私钥:opensslpkcs8-topk8-informPEM-outformPEM-nocrypt-inprivatekeyfilename-outexportfilename得到pkcs8格式的私钥后,我们去掉文件头尾,然后放入如下Java代码:try{Ciphercipher=Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");cipher.init(Cipher.DECRYPT_MODE,KeyFactory.getInstance("RSA").generatePrivate(newPKCS8EncodedKeySpec(Base64.getDecoder().decode("这是私钥"))));Stringplaintext=newString(cipher.doFinal(Base64.getDecoder().decode("这是密文".getBytes())),"UTF-8");System.out.println(plaintext);}catch(Exceptione){System.out.println("Decryptionerror:"+e.toString());}和上面的AES解密类似,只是KeyFactory读取privatek的部分添加了PKCS8格式的ey,这样我们就完成了Java端的RSA解密。上面我们用最简单的方式实现了js端加密,java端解密的过程。感兴趣的朋友可以在这里下载完整的代码自行验证:https://github.com/fengerzh/encdec
