# 简介

# ChaCha20

ChaCha 系列流密码,作为 Salsa20 密码的改良版,具有更强的抵抗密码分析攻击的特性, 20 表示该算法有 20 轮的加密计算。

由于是流密码,故以字节为单位进行加密,安全性的关键体现在密钥流生成的过程,即所依赖的 伪随机数 生成器(PRNG)的强度,加密过程即是将密钥流与明文逐字节异或得到密文,反之,解密是将密文再与密钥流做一次异或运算得到明文。

# Poly1305 消息认证码

消息认证码 (带密钥的 Hash函数 ): 密码学中,通信实体双方使用的一种验证机制,保证消息 数据完整性 的一种工具。构造方法由 M.Bellare 提出,安全性依赖于 Hash 函数,故也称带密钥的 Hash 函数。消息认证码是基于密钥和消息摘要所获得的一个值,可用于数据源发认证和完整性校验。

Poly1305 Daniel.J.Bernstein 创建的消息认证码,可用于检测消息的完整性和验证消息的真实性,现常在网络安全协议(SSL/TLS)中与 Salsa20ChaCha20 流密码结合使用。

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