程序员必须知道的加密、解密和签名算法

程序员必须知道的加密、解密和签名算法

首页冒险解谜水滴解密更新时间:2024-05-09
1. 对称加密

对称加密,加密和解密使用相同的秘钥,加密速度快、效率高。常见的有 DES(淘汰)、3DES(淘汰)、AES(用于替代 DES,是目前常用的)等。

加密解密

1.1. DES(Data Encryption Standard)

DES 现在认为是一种不安全的加密算法,已经有用穷举法攻破 DES 密码的报道了。3DES 是 DES 的加强版本(也被淘汰),是 DES 向 AES 过渡的加密算法。

1.2. AES(Advanced Encryption Standard)

AES 把明文按每组16个字节分成一组一组的、长度相等的数据,每次加密一组,直到加密完整个明文。在 AES 标准中,分组长度只能是128位,但是密钥的长度可以使用128位、192位或256位。

下面先来了解“分组加密机制、填充模式、初始向量、加密模式”等基本概念,最后给出 Java 代码示例。

1.2.1. 分组密码体制

分组密码体制就是指将明文切成一段一段的来加密,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节了,就需要用 Padding 来把这段数据填满16个字节,然后再把一段一段的密文拼起来形成最终密文的加密方式。

1.2.2. 填充模式 Padding

Padding 就是用来把不满16个字节的分组数据填满16个字节用的,它有三种模式 PKCS5、PKCS7 和 NOPADDING。

在 PKCS5 模式下,有这样一种特殊情况,假设最后一段数据的内容刚好就是16个16,这时解密端怎么区分是填充还是数据呢?

对于这种情况,PKCS5 模式会自动帮我们在最后一段数据后再添加16个字节的数据,而且填充数据也是16个16,这样解密端就能知道谁是有效数据谁是填充数据了。同样的道理,PKCS7 最后一段数据的内容是16个0。

解密端需要使用和加密端同样的 Padding 模式,才能准确的识别有效数据和填充数据。开发通常采用 PKCS7 Padding 模式。

1.2.3. 初始向量 IV

初始向量 IV 的作用是使加密更加安全可靠。使用 AES 加密时要主动提供初始向量,而且只需提供一个初始向量就够了,后面每段数据的加密向量都是前面一段的密文。初始向量 IV 的长度规定为128位16个字节,初始向量通常采用随机生成。

1.2.4. 密钥

AES 要求密钥的长度可以是128位、192位或者256位,位数越高,加密强度越大,但是加密的效率自然会低一些,因此要做好衡量。

1.2.5. 分组加密模式

分组加密算法只能对固定长度的分组进行加密,面对超过分组长度的明文,就需要对分组密码算法进行迭代,以便将很长的明文全部加密。而迭代的方法就称为“分组加密的模式”。分组密码的模式有很多,常见的有:

AES 和 RSA 都属于分组加密算法。

1.2.6. AES Java 示例

AES-128-CBC 加解密

public static String cbcEncrypt(String plain, String key, String ivSeed) { Assert.notNull(plain, "plain must not be null"); Assert.notNull(key, "key must not be null"); Assert.notNull(ivSeed, "ivSeed must not be null"); Assert.isTrue(ivSeed.length() == 16, "ivSeed must be 16 bytes"); String base64 = null; try { // 生成秘钥 SecretKeySpec keySpec = createKey(key); // 设置算法/模式/填充方式 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 设置偏移 IvParameterSpec iv = new IvParameterSpec(ivSeed.getbytes(StandardCharsets.UTF_8)); // 加密模式 cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); // 加密 byte[] encrypted = cipher.doFinal(plain.getBytes(StandardCharsets.UTF_8)); // 转 base64 base64 = Base64.getEncoder().encodeToString(encrypted); } catch (Exception ex) { log.error("exception: {}", ex.getMessage()); } return base64; } public static String cbcDecrypt(String base64, String key, String ivSeed) { Assert.notNull(base64, "base64 must not be null"); Assert.notNull(key, "key must not be null"); Assert.notNull(ivSeed, "ivSeed must not be null"); Assert.isTrue(ivSeed.length() == 16, "ivSeed must be 16 bytes"); String plain = null; try { // base64 解码 byte[] decodedBase64 = Base64.getdecoder().decode(base64); // 生成秘钥 SecretKeySpec keySpec = createKey(key, isBase64Key); if(null != keySpec) { // 设置偏移 IvParameterSpec iv = new IvParameterSpec(ivSeed.getBytes(StandardCharsets.UTF_8)); // 设置算法/模式/填充方式 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 解密模式 cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); // 解密 byte[] decrypted = cipher.doFinal(data); // 转 base64 plain = new String(decrypted, StandardCharsets.UTF_8); } } catch (Exception ex) { log.error("exception: {}", ex.getMessage()); } return plain; } private static SecretKeySpec createKey(String key) { Assert.notNull(key, "key must not be null"); byte[] bytesKey = key.getBytes(StandardCharsets.UTF_8); Assert.isTrue(bytesKey.length == 16, "key must be 16 bytes"); // 生成秘钥 return new SecretKeySpec(bytesKey, "AES"); }2. 非对称加密

非对称加密算法,需要两个密钥, 一个是公钥 (public key),公开,任何人都可以获取;另一个是 私钥 (private key),不公开,由个人保存在安全的地方。公钥用于加密,私钥用于解密。

RSA (三位数学家名字的缩写)算法是第一个能同时用于 加密数字签名 的非对称加密算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推荐为公钥数据加密标准。

2.1. 使用场景

假设 A 和 B 之间要进行加密通信,那么:

(1)B向A发送加密数据

在这个过程中,只有2次传递过程,第一次是 A 传递公钥给 B,第二次是 B 传递加密消息给A,即使都被截获,也没有危险性,因为只有 A 的私钥才能对消息进行解密,防止了消息内容的泄露。

(2)A向B发送“已收到”回复

在这个过程中,算上前面的传递公钥,也只有2次传递过程,一次是传递公钥,第二次就是 A 传递加签的消息和消息本身给 B,即使都被敌方截获,也同样没有危险性,因为只有 A 的私钥才能对消息进行签名,即使知道了消息内容,也无法伪造带签名的回复给 B,防止了消息内容的篡改。

但是,综合上面两个场景会发现:

所以在实际应用中,要根据情况使用,可以双方同时使用加密和签名,比如 A 和 B 都有一套自己的公钥和私钥,当 A 要给 B 发送消息时,先用 B 的公钥对消息加密,再对加密的消息使用 A 的私钥加签名,达到既不泄露也不被篡改,更能保证消息的安全性。

2.2. RAS 加密算法2.2.1. 填充模式 Padding

Padding 常见模式如下表:

Padding 模式

RSA 常用的加密填充模式

2.2.2. RSA Java 示例

/** * 公钥加密 */ public static String ecbEncrypt(String data, String publicKeyBase64) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, toPublicKey(publicKeyBase64)); byte[] bytes cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(bytes); } /** * 私钥解密 */ public static String ecbDecrypt(String base64, String privateKeyBase64) throws IllegalBlockSizeException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException { byte[] bytes = Base64.getDecoder().decode(base64.getBytes()); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, toPrivateKey(privateKeyBase64)); return new String(cipher.doFinal(data)); } /** * 生成随机密钥对 */ public static hashMap<String, String> randomKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); generator.initialize(2048); KeyPair pair = generator.generateKeyPair(); if (null == pair) { return null; } PrivateKey pvt = pair.getPrivate(); PublicKey pub = pair.getPublic(); Base64.Encoder encoder = Base64.getEncoder(); String pvtVal = encoder.encodeToString(pvt.getEncoded()); String pubVal = encoder.encodeToString(pub.getEncoded()); HashMap<String, String> rsaKeyMap = new HashMap<>(2); rsaKeyMap.put("privateKeyBase64", pvtVal); rsaKeyMap.put("publicKeyBase64", pubVal); return rsaKeyMap; } /** * privateKeyBase64 私钥转为 PrivateKey 对象 */ private static PrivateKey toPrivateKey(String privateKeyBase64) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] bytes = Base64.getDecoder().decode(privateKeyBase64.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(keySpec); } /** * publicKeyBase64 公钥转为 PublicKey 对象 */ private static PublicKey toPublicKey(String publicKeyBase64) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] bytes = Base64.getDecoder().decode(publicKeyBase64.getBytes()); X509EncodedKeySpec ks = new X509EncodedKeySpec(bytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePublic(ks); }3. 签名

加密是为了防止信息被泄露,而签名是为了防止信息被篡改和伪造。

签名 & 验签

3.1. 摘要算法

哈希函数(Hash function),又称散列函数、散列算法,也叫摘要算法,它是一种不可逆的信息摘要算法。

好的散列算法具备如下特性:

常见的用途:

常见的散列算法有”报文摘要算法 MD“、”安全散列算法 SHA“,以及”消息认证码算法 MAC“。

摘要算法

3.1.1. 报文摘要算法(MD系列)

信息摘要算法(Message-Digest Algorithm)。最常用的是 MD5 (Message-Digest Algorithm 5),是一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),常用于确保信息传输完整一致。

3.1.2. 安全散列算法(SHA系列)

安全散列算法(Secure Hash Algorithm)是一种不可逆的信息安全算法,经过量化运算和转换,可以把任意长度的数据生成不可逆的、固定长度的字符串,这个固定长度的字符串就是对相应的原始输入字符串的散列(也称为摘要),可以作为信息的指纹。

SHA-224,SHA-256,SHA-384,SHA-512 统称为 SHA-2,而 SHA-1 算法已经不够安全,不建议继续使用。

3.1.3. 消息认证码(MAC系列)

消息认证码算法(Message Authentication Code)是含有加密密钥的散列算法,它在 MD 和 SHA 算法特性的基础上加入了加密密钥,通过特别的计算方式来构造消息认证码(MAC)的方法。 因 MAC 算法融合了密钥散列函数,通常也称为 HMAC 算法(Hash-based Message Authentication Code,散列消息认证码)。

常见的有:HMAC-SHA224、HMAC-SHA256、HMAC-SHA384、HMAC-SHA512

3.2. 签名验签原理3.2.1. 签名

对需要发送的报文 originData 计算摘要(相关摘要算法有 md5、sha256等)特征值 signBlock。 使用私钥 privateKey 对 signBlock 加密获得数字签名 signatureData。 将 signatureData 与 originData 打包发一起送给对方。

3.2.2. 验签

接收方接收到数据后,把消息拆分为 signatureData 与 originData 。 对 originData 计算特征值 signBlock,使用的算法必须要和发送方一致。 使用公钥 publicKey 对 signatureData 解密,获得 signBlock1。 比较 signBlock 和 signBlock1,若匹配则验证成功,报文未被篡改。

3.3. RSA 签名示例

/** * 用私钥对数据进行签名并返回签名后的base64 * @param data 代签名的字符串 * @param base64PrivateKey 私钥 */ public static String sign(String data, String base64PrivateKey) throws InvalidKeySpecException, InvalidKeyException, NoSuchAlgorithmException, SignatureException { PrivateKey key = toPrivateKey(base64PrivateKey); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(key); signature.update(data.getBytes()); return new String(Base64.getEncoder().encode(signature.sign())); } /** * 验签 * @param data 原始数据 * @param base64PublicKey 公钥 * @param sign 私钥签名后的数据 */ public static boolean verify(String data, String base64PublicKey, String sign) throws InvalidKeySpecException, InvalidKeyException, NoSuchAlgorithmException, SignatureException { PublicKey key = toPublicKey(base64PublicKey); Signature signature = Signature.getInstance("SHA256withRSA"); signature.initVerify(key); signature.update(data.getBytes()); return signature.verify(Base64.getDecoder().decode(sign.getBytes())); } /** * base64PrivateKey 私钥转为 PrivateKey 对象 */ private static PrivateKey toPrivateKey(String base64PrivateKey) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] bytes = Base64.getDecoder().decode(base64PrivateKey.getBytes()); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(keySpec); } /** * base64PublicKey 公钥转为 PublicKey 对象 */ private static PublicKey toPublicKey(String base64PublicKey) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] bytes = Base64.getDecoder().decode(base64PublicKey.getBytes()); X509EncodedKeySpec ks = new X509EncodedKeySpec(bytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePublic(ks); }小结

本文主要介绍了常用的对称加密算法、非对称加密算法;常用的摘要算法、签名算法。以及使用算法需要了解的基本概念(比如填充模式、IV等),算法的使用场景,并且分别给出了 Java 示例代码。希望对各位小伙伴们有帮助哦[中国赞][中国赞][中国赞]

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved