# 简介
# ChaCha20
ChaCha
系列流密码,作为 Salsa20
密码的改良版,具有更强的抵抗密码分析攻击的特性, 20
表示该算法有 20 轮的加密计算。
由于是流密码,故以字节为单位进行加密,安全性的关键体现在密钥流生成的过程,即所依赖的 伪随机数
生成器(PRNG)的强度,加密过程即是将密钥流与明文逐字节异或得到密文,反之,解密是将密文再与密钥流做一次异或运算得到明文。
# Poly1305 消息认证码
消息认证码
(带密钥的 Hash函数
): 密码学中,通信实体双方使用的一种验证机制,保证消息 数据完整性
的一种工具。构造方法由 M.Bellare 提出,安全性依赖于 Hash 函数,故也称带密钥的 Hash 函数。消息认证码是基于密钥和消息摘要所获得的一个值,可用于数据源发认证和完整性校验。
Poly1305
是 Daniel.J.Bernstein 创建的消息认证码,可用于检测消息的完整性和验证消息的真实性,现常在网络安全协议(SSL/TLS)中与 Salsa20
或 ChaCha20
流密码结合使用。
Poly1305
消息认证码的输入为 32 字节(256bit)的密钥和任意长度的消息比特流,经过一系列计算生成 16 字节(128bit)的摘要。
# 源代码
# java
crypto 库默认加密后的结构为 ciphertext + tag
ChaCha20Poly1305.java
import java.io.IOException; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.security.GeneralSecurityException; | |
import java.security.InvalidAlgorithmParameterException; | |
import java.security.InvalidKeyException; | |
import java.security.NoSuchAlgorithmException; | |
import java.security.SecureRandom; | |
import java.security.spec.AlgorithmParameterSpec; | |
import javax.crypto.Cipher; | |
import javax.crypto.KeyGenerator; | |
import javax.crypto.NoSuchPaddingException; | |
import javax.crypto.spec.IvParameterSpec; | |
import javax.crypto.spec.SecretKeySpec; | |
public class ChaCha20Poly1305 { | |
private static final String ENCRYPT_ALGO = "ChaCha20-Poly1305/None/NoPadding"; | |
public static Map<String, Object> encrypt(byte[] plaintext, byte[] key, String header) | |
throws GeneralSecurityException, IOException { | |
byte[] nonceBytes = new byte[12]; | |
new SecureRandom().nextBytes(nonceBytes); | |
byte[] data = encrypt(plaintext, key, nonceBytes, Utils.hex2bytes(header)); | |
Map<String, Object> jsonMap = new HashMap<>(); | |
jsonMap.put("nonce", Utils.bytes2hex(nonceBytes)); | |
jsonMap.put("header", Utils.bytes2hex(header.getBytes())); | |
jsonMap.put("data", Utils.bytes2hex(data)); | |
return jsonMap; | |
} | |
public static String decrypt2String(byte[] nonce, byte[] AAD, byte[] cipherData, byte[] key) | |
throws GeneralSecurityException, IOException { | |
byte[] decryptedText = decrypt(cipherData, key, nonce, AAD); | |
return new String(decryptedText); | |
} | |
public static byte[] decrypt2Bytes(byte[] nonce, byte[] AAD, byte[] cipherData, byte[] key) | |
throws GeneralSecurityException, IOException { | |
byte[] decryptedText = decrypt(cipherData, key, nonce, AAD); | |
return decryptedText; | |
} | |
public static byte[] encrypt(byte[] plaintext, byte[] key, byte[] nonceBytes, byte[] AAD) | |
throws GeneralSecurityException, IOException { | |
try { | |
// Get Cipher Instance | |
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); | |
// Create IvParamterSpec | |
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes); | |
// Create SecretKeySpec | |
SecretKeySpec keySpec = new SecretKeySpec(key, ENCRYPT_ALGO); | |
// Initialize Cipher for ENCRYPT_MODE | |
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec); | |
cipher.updateAAD(AAD); | |
// Perform Encryption | |
byte[] cipherText = cipher.doFinal(plaintext); | |
return cipherText; | |
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { | |
throw new IllegalStateException(e); | |
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { | |
throw new IllegalArgumentException(e); | |
} | |
} | |
public static byte[] decrypt(byte[] cipherText, byte[] key, byte[] nonceBytes, byte[] AAD) | |
throws GeneralSecurityException, IOException { | |
try { | |
// Get Cipher Instance | |
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); | |
// Create IvParamterSpec | |
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes); | |
// Create SecretKeySpec | |
SecretKeySpec keySpec = new SecretKeySpec(key, ENCRYPT_ALGO); | |
// Initialize Cipher for DECRYPT_MODE | |
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec); | |
cipher.updateAAD(AAD); | |
// Perform Decryption | |
byte[] decryptedText = cipher.doFinal(cipherText); | |
return decryptedText; | |
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) { | |
throw new IllegalStateException(e); | |
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) { | |
throw new IllegalArgumentException(e); | |
} | |
} | |
/** | |
* 生成 ChaCha20 密钥 (256bit) | |
* | |
* @return | |
* @throws NoSuchAlgorithmException | |
*/ | |
public static byte[] generateKey() throws NoSuchAlgorithmException { | |
KeyGenerator keyGen = KeyGenerator.getInstance(ENCRYPT_ALGO); | |
keyGen.init(256, SecureRandom.getInstanceStrong()); | |
return keyGen.generateKey().getEncoded(); | |
} | |
/** | |
* 测试 | |
* @throws Exception | |
*/ | |
@Test | |
public void ChaCha20Poly1305Test() throws Exception { | |
String plaintext = "ChaCha20Poly1305Test"; | |
KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20"); | |
keyGen.init(256, SecureRandom.getInstanceStrong()); | |
SecretKey key = keyGen.generateKey(); | |
byte[] nonceBytes = new byte[12]; | |
new SecureRandom().nextBytes(nonceBytes); | |
byte[] AAD = Utils.hex2bytes(Utils.md5("header")); | |
byte[] ciphertext = ChaCha20Poly1305.encrypt(plaintext.getBytes(), key.getEncoded(), nonceBytes, AAD); | |
String text = ChaCha20Poly1305.decrypt2String(ciphertext, key.getEncoded(), nonceBytes, AAD); | |
assertEquals(plaintext, text); | |
} | |
} |
Utils.java
import java.math.BigInteger; | |
import java.security.MessageDigest; | |
public class Utils { | |
public static String md5(String content) { | |
byte[] hash; | |
try { | |
hash = MessageDigest.getInstance("MD5").digest(content.getBytes("utf-8")); | |
} catch (Exception e) { | |
throw new RuntimeException("NoSuchAlgorithmException", e); | |
} | |
return bytes2hex(hash); | |
} | |
/** | |
* | |
* @param bytes the byte [] | |
* @return the hex string | |
*/ | |
public static String bytes2hex(byte[] bytes) { | |
return new BigInteger(1, bytes).toString(16); | |
} | |
/** | |
* To byte array byte [ ]. | |
* | |
* @param hexString the hex string | |
* @return the byte [] | |
*/ | |
public static byte[] hex2bytes(String hex) { | |
hex = hex.length() % 2 != 0 ? "0" + hex : hex; | |
byte[] b = new byte[hex.length() / 2]; | |
for (int i = 0; i < b.length; i++) { | |
int index = i * 2; | |
int v = Integer.parseInt(hex.substring(index, index + 2), 16); | |
b[i] = (byte) v; | |
} | |
return b; | |
} | |
/** | |
* 合并数组 System.arraycopy () | |
* | |
* @param byte1 | |
* @param byte2 | |
* @return | |
*/ | |
public static byte[] byteMerger(byte[] byte1, byte[] byte2) { | |
byte[] byte3 = new byte[byte1.length + byte2.length]; | |
System.arraycopy(byte1, 0, byte3, 0, byte1.length); | |
System.arraycopy(byte2, 0, byte3, byte1.length, byte2.length); | |
return byte3; | |
} | |
} |
# python
pycryptodome 库默认加密后的结构为 ciphertext, tag
# pip install pycryptodome | |
from Crypto.Cipher import ChaCha20_Poly1305 | |
from Crypto.Random import get_random_bytes | |
def chacha20_poly1305_en_func(plaintext: bytes, key: bytes, associatedData: bytes): | |
#header = b"header" | |
#plaintext = b'Attack at dawn' | |
#key = get_random_bytes(32) | |
cipher = ChaCha20_Poly1305.new(key=key) | |
cipher.update(associatedData) | |
ciphertext, tag = cipher.encrypt_and_digest(plaintext) | |
return cipher.nonce.hex(), (ciphertext + tag).hex() | |
def chacha20_poly1305_de_func(ciphertext: bytes, key: bytes, nonce: bytes, associatedData: bytes, tag: bytes): | |
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) | |
cipher.update(associatedData) | |
plaintext = cipher.decrypt_and_verify(ciphertext, tag) | |
return plaintext |
# 注记
加密库处理结果为 byte/byte [],需要编码 / 解码(base64/hex)才能互相识别