# 简介
# 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)才能互相识别