在本文中,我们将向您展示如何使用RFC 7539中定义的ChaCha20流密码对消息进行加密和解密。
PS ChaCha20流密码可从Java 11获得,请参阅JEP 329 。
注意
您可能对此ChaCha20-Poly1305加密示例感兴趣
1.如何运作?
1.1 ChaCha20加密和解密的输入:
- 256位密钥(32字节)
- 96位随机数(12个字节)
- 32位初始计数(4个字节)
1.2 ChaCha20加密。
(plain text) + (secrect key | nonce | initial count) -> `ChaCha20` -> ciphertext (encrypted text).
1.2 ChaCha20解密。
ciphertext + (secrect key | nonce | initial count) -> `ChaCha20` -> plain text.
1.3 ChaCha20
加密使用密钥和IV(初始化值,随机数+初始计数)将纯文本加密为等长的密文。
1.4每次加密的随机数和秘密密钥必须唯一。 随机数和初始计数可以公开知道,但是密钥必须是私有的并对其保密。
ChaCha20 Java实现
下载JDK源代码,并找到用于ChaCha20算法实现的此类ChaCha20Cipher
。
package com.sun.crypto.provider;
/**
* Implementation of the ChaCha20 cipher, as described in RFC 7539.
*
* @since 11
*/
abstract class ChaCha20Cipher extends CipherSpi {
//...
}
2. ChaCha20加密和解密。
2.1一个使用ChaCha20算法加密和解密消息的Java示例。
package com.mkyong.java11.jep329.chacha20;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;
/*
The inputs to ChaCha20 encryption, specified by RFC 7539, are:
- A 256-bit secret key (32 bytes)
- A 96-bit nonce (12 bytes)
- A 32-bit initial count (4 bytes)
*/
public class ChaCha20 {
private static final String ENCRYPT_ALGO = "ChaCha20";
public byte[] encrypt(byte[] pText, SecretKey key, byte[] nonce, int counter) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);
cipher.init(Cipher.ENCRYPT_MODE, key, param);
byte[] encryptedText = cipher.doFinal(pText);
return encryptedText;
}
public byte[] decrypt(byte[] cText, SecretKey key, byte[] nonce, int counter) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);
cipher.init(Cipher.DECRYPT_MODE, key, param);
byte[] decryptedText = cipher.doFinal(cText);
return decryptedText;
}
}
2.1测试。
package com.mkyong.java11.jep329.chacha20;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class TestChaCha20 {
public static void main(String[] args) throws Exception {
String input = "Java & ChaCha20 encryption example.";
ChaCha20 cipher = new ChaCha20();
SecretKey key = getKey(); // 256-bit secret key (32 bytes)
byte[] nonce = getNonce(); // 96-bit nonce (12 bytes)
int counter = 1; // 32-bit initial count (8 bytes)
System.out.println("Input : " + input);
System.out.println("Input (hex): " + convertBytesToHex(input.getBytes()));
System.out.println("\n---Encryption---");
byte[] cText = cipher.encrypt(input.getBytes(), key, nonce, counter); // encrypt
System.out.println("Key (hex): " + convertBytesToHex(key.getEncoded()));
System.out.println("Nonce (hex): " + convertBytesToHex(nonce));
System.out.println("Counter : " + counter);
System.out.println("Encrypted (hex): " + convertBytesToHex(cText));
System.out.println("\n---Decryption---");
byte[] pText = cipher.decrypt(cText, key, nonce, counter); // decrypt
System.out.println("Key (hex): " + convertBytesToHex(key.getEncoded()));
System.out.println("Nonce (hex): " + convertBytesToHex(nonce));
System.out.println("Counter : " + counter);
System.out.println("Decrypted (hex): " + convertBytesToHex(pText));
System.out.println("Decrypted : " + new String(pText));
}
// A 256-bit secret key (32 bytes)
private static SecretKey getKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
keyGen.init(256, SecureRandom.getInstanceStrong());
return keyGen.generateKey();
}
// 96-bit nonce (12 bytes)
private static byte[] getNonce() {
byte[] newNonce = new byte[12];
new SecureRandom().nextBytes(newNonce);
return newNonce;
}
private static String convertBytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte temp : bytes) {
result.append(String.format("%02x", temp));
}
return result.toString();
}
}
输出量
Input : Java & ChaCha20 encryption example.
Input (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
---Encryption---
Key (hex): ee416df8b5154a4ac48f3930fcfa53ef7f677c8fd7cd093f1328eedfd831db1a
Nonce (hex): 9806308f4d1732d2d39beaba
Counter : 1
Encrypted (hex): 2149db2c32bf82f9e8dc0a709d8c15d5a22eb79d5f692e04f070d46cc7e264631f85e0
---Decryption---
Key (hex): ee416df8b5154a4ac48f3930fcfa53ef7f677c8fd7cd093f1328eedfd831db1a
Nonce (hex): 9806308f4d1732d2d39beaba
Counter : 1
Decrypted (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
Decrypted : Java & ChaCha20 encryption example.
key + nonce + counter
必须相同,才能进行加密和解密,不同的单个位将产生不同的结果。
3. ChaCha20…版本2
3.1在此示例中,我们使用System.arraycopy
将现时数和初始化计数器附加到密文(加密消息)的末尾,因此我们只需要用于解密的密钥即可。
package com.mkyong.java11.jep329.chacha20v2;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.ChaCha20ParameterSpec;
import java.nio.ByteBuffer;
public class ChaCha20v2 {
private static final String ENCRYPT_ALGO = "ChaCha20";
private static final int LEN_NONCE = 12;
private static final int LEN_COUNTER = 4;
public byte[] encrypt(byte[] pText, SecretKey key, byte[] nonce, int counter) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, counter);
cipher.init(Cipher.ENCRYPT_MODE, key, param);
byte[] encryptedText = cipher.doFinal(pText);
// append nonce + count
byte[] output = new byte[encryptedText.length + LEN_NONCE + LEN_COUNTER];
System.arraycopy(encryptedText, 0, output, 0, encryptedText.length);
System.arraycopy(nonce, 0, output, encryptedText.length, LEN_NONCE);
// convert int to byte[]
byte[] counterByteArray = ByteBuffer.allocate(4).putInt(counter).array();
System.arraycopy(counterByteArray, 0, output, encryptedText.length + LEN_NONCE, LEN_COUNTER);
return output;
}
public byte[] decrypt(byte[] cText, SecretKey key) throws Exception {
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
// get only the encrypted text
byte[] encryptedText = new byte[cText.length - (LEN_NONCE + LEN_COUNTER)];
System.arraycopy(cText, 0, encryptedText, 0, cText.length - (LEN_NONCE + LEN_COUNTER));
// get nonce
byte[] nonce = new byte[12];
System.arraycopy(cText, encryptedText.length, nonce, 0, LEN_NONCE);
// get initial counter
byte[] counter = new byte[4];
System.arraycopy(cText, encryptedText.length + LEN_NONCE, counter, 0, LEN_COUNTER);
/// convert byte array to int
int ic = ByteBuffer.wrap(counter).getInt();
ChaCha20ParameterSpec param = new ChaCha20ParameterSpec(nonce, ic);
cipher.init(Cipher.DECRYPT_MODE, key, param);
byte[] decryptedText = cipher.doFinal(encryptedText);
return decryptedText;
}
}
3.2对于解密,我们只需要提供秘密密钥,因为随机数和初始计数器位于加密文本或密文的末尾。
package com.mkyong.java11.jep329.chacha20v2;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class TestChaCha20v2 {
public static void main(String[] args) throws Exception {
String input = "Java & ChaCha20 encryption example.";
ChaCha20v2 cipher = new ChaCha20v2();
SecretKey key = getKey(); // 256-bit secret key (32 bytes)
byte[] nonce = getNonce(); // 96-bit nonce (12 bytes)
int counter = 1; // 32-bit initial count (8 bytes)
System.out.println("Input : " + input);
System.out.println("Input (hex): " + convertBytesToHex(input.getBytes()));
System.out.println("\n---Encryption---");
byte[] cText = cipher.encrypt(input.getBytes(), key, nonce, counter); // encrypt
System.out.println("Key (hex): " + convertBytesToHex(key.getEncoded()));
System.out.println("Nonce (hex): " + convertBytesToHex(nonce));
System.out.println("Counter : " + counter);
System.out.println("Encrypted (hex): " + convertBytesToHex(cText));
System.out.println("\n---Decryption---");
byte[] pText = cipher.decrypt(cText, key); // decrypt
System.out.println("Key (hex): " + convertBytesToHex(key.getEncoded()));
System.out.println("Decrypted (hex): " + convertBytesToHex(pText));
System.out.println("Decrypted : " + new String(pText));
}
private static String convertBytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte temp : bytes) {
result.append(String.format("%02x", temp));
}
return result.toString();
}
// A 256-bit secret key (32 bytes)
private static SecretKey getKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
keyGen.init(256, SecureRandom.getInstanceStrong());
return keyGen.generateKey();
}
// 96-bit nonce (12 bytes)
private static byte[] getNonce() {
byte[] newNonce = new byte[12];
new SecureRandom().nextBytes(newNonce);
return newNonce;
}
}
输出量
Input : Java & ChaCha20 encryption example.
Input (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
---Encryption---
Key (hex): f95fd5b41783595e41f4cbcd8dc26a782599184e97ccd768ac531aae729781d3
Nonce (hex): 84133f2261ef44796e3669dc
Counter : 1
Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e84133f2261ef44796e3669dc00000001
---Decryption---
Key (hex): f95fd5b41783595e41f4cbcd8dc26a782599184e97ccd768ac531aae729781d3
Decrypted (hex): 4a617661202620436861436861323020656e6372797074696f6e206578616d706c652e
Decrypted : Java & ChaCha20 encryption example.
查看加密的文本; 它结合了加密的消息,随机数(12个字节)和初始计数器(4个字节)。
Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e84133f2261ef44796e3669dc00000001
## split view
Encrypted (hex): 7738807b409f3349dbefbeae988482e0e5959c35ee8f8ee8987357db459e10d7fb8c7e | 84133f2261ef44796e3669dc | 00000001
注意
如果发现任何错误,请在下面发表评论,谢谢。
下载源代码
$ git clone https://github.com/mkyong/core-java.git
$ cd java-11
$ cd / src / main / java / com / mkyong / java11 / jep329
参考文献
- ChaCha20
- RFC 7539
- Cloudflare –需要两个到ChaCha(Poly)
- ChaCha20计数器实际上通过迭代增加吗?
- Java:ChaCha20带Poly1305作为MAC,用于通用文件加密
翻译自: https://mkyong.com/java/java-11-chacha20-stream-cipher-examples/