使用 ChaCha20 加密和解密字符串

Xar*_*mer 0 encryption android bouncycastle

我想使用chacha20解密和加密字符串

BouncyCastleProvider 正在使用 chacha20 技术。所以我把它包括在罐子里。并尝试了代码但无法工作。

测试版

public class PBE extends AppCompatActivity {

    private static final String salt = "A long, but constant phrase that will be used each time as the salt.";
    private static final int iterations = 2000;
    private static final int keyLength = 256;
    private static final SecureRandom random = new SecureRandom();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pbe);

        try {
            Security.insertProviderAt(new BouncyCastleProvider(), 1);
            //Security.addProvider(new BouncyCastleProvider());

            String passphrase = "The quick brown fox jumped over the lazy brown dog";
            String plaintext = "Hello";
            byte [] ciphertext = encrypt(passphrase, plaintext);
            String recoveredPlaintext = decrypt(passphrase, ciphertext);

            TextView decryptedTv = (TextView) findViewById(R.id.tv_decrypt);

            decryptedTv.setText(recoveredPlaintext);

            System.out.println(recoveredPlaintext);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    private static byte [] encrypt(String passphrase, String plaintext) throws Exception {
        SecretKey key = generateKey(passphrase);

        Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");//,new BouncyCastleProvider());
        cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random);
        return cipher.doFinal(plaintext.getBytes());
    }

    private static String decrypt(String passphrase, byte [] ciphertext) throws Exception {
        SecretKey key = generateKey(passphrase);

        Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");// , new BouncyCastleProvider());
        cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random);
        return new String(cipher.doFinal(ciphertext));
    }

    private static SecretKey generateKey(String passphrase) throws Exception {
        PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
        return keyFactory.generateSecret(keySpec);
    }

    private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
        byte [] ivBytes = new byte[cipher.getBlockSize()];
        random.nextBytes(ivBytes);
        return new IvParameterSpec(ivBytes);
    }

}
Run Code Online (Sandbox Code Playgroud)

但它没有给我正确的结果..

在此处输入图片说明

编辑和更新代码

public class ChaCha20Encryptor implements Encryptor {

    private final byte randomIvBytes[] = {0, 1, 2, 3, 4, 5, 6, 7};

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    @Override
    public byte[] encrypt(byte[] data, byte[] randomKeyBytes) throws IOException, InvalidKeyException,
            InvalidAlgorithmParameterException, InvalidCipherTextException {

        ChaChaEngine cipher = new ChaChaEngine();
        CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
        cipher.init(true, new ParametersWithIV(cp , randomIvBytes));
        //cipher.init(true, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));

        byte[] result = new byte[data.length];
        cipher.processBytes(data, 0, data.length, result, 0);
        return result;
    }

    @Override
    public byte[] decrypt(byte[] data, byte[] randomKeyBytes)
            throws InvalidKeyException, InvalidAlgorithmParameterException, IOException,
            IllegalStateException, InvalidCipherTextException {

        ChaChaEngine cipher = new ChaChaEngine();
        CipherParameters cp = new KeyParameter(getMyKey(randomKeyBytes));
        cipher.init(false, new ParametersWithIV(cp , randomIvBytes));
        //cipher.init(false, new ParametersWithIV(new KeyParameter(randomKeyBytes), randomIvBytes));

        byte[] result = new byte[data.length];
        cipher.processBytes(data, 0, data.length, result, 0);
        return result;
    }

    @Override
    public int getKeyLength() {
        return 32;
    }

    @Override
    public String toString() {
        return "ChaCha20()";
    }

    private static byte[] getMyKey(byte[] key){
        try {
            //byte[] key = encodekey.getBytes("UTF-8");
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); // use only first 128 bit
        }
        catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        }
        return key;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我只有解密问题。它显示密钥必须是 128 或 256 位的错误。我究竟做错了什么。

Sap*_*asu 6

2019 年 12 月 24 日更新(更正)

与 AES 中的其他一些模式(如 CBC)不同,GCM 模式不需要 IV 不可预测(与 Chacha20-Poly1305 相同)。唯一的要求是对于使用给定密钥的每次调用,IV(对于 AES)或随机数(对于 Chacha20-Poly1305)必须是唯一的。如果对给定的密钥重复一次,则安全性可能会受到影响。实现这一目标的一种简单方法是使用来自强伪随机数生成器的随机 IV 或 nonce,如下所示。IV 或 nonce 碰撞的概率(假设一个强随机源)最多为 2^-32,这足以阻止攻击者。

使用序列或时间戳作为 IV 或随机数也是可能的,但它可能不像听起来那么简单。例如,如果系统没有正确跟踪已在持久存储中用作 IV 的序列,则调用可能会在系统重新启动后重复 IV。同样,没有完美的时钟。电脑时钟重新调整等。

此外,应在每 2^32 次调用后轮换密钥。

SecureRandom.getInstanceStrong() 可用于生成加密强随机随机数。


原答案

现在支持ChaCha20的是Java 11。这里是一个使用ChaCha20-Poly1305加密和解密的示例程序。

在 AES-GCM(一种经过身份验证的分组密码算法)上使用 ChaCha20-Poly1305(一种基于流密码的经过身份验证的加密算法)的可能原因是:

  1. 当 CPU 不提供专用 AES 指令时,ChaCha20-Poly1305 几乎比 AES 快 3 倍。Intel 处理器提供 AES-NI 指令集[1]
  2. 与 AES-GCM 的 IV 不同,ChaCha20-Poly1305 不需要随机数是不可预测的/随机的。因此可以避免运行伪随机数生成器的开销[2]
  3. 与 AES [1]不同,ChaCha20 不易受到缓存冲突定时攻击的影响

    package com.sapbasu.javastudy;
    
    import java.lang.reflect.Field;
    import java.math.BigInteger;
    import java.util.Arrays;
    import java.util.Objects;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import javax.security.auth.Destroyable;
    
    /**
     * 
     * The possible reasons for using ChaCha20-Poly1305 which is a
     * stream cipher based authenticated encryption algorithm
     * 1. If the CPU does not provide dedicated AES instructions,
     *    ChaCha20 is faster than AES
     * 2. ChaCha20 is not vulnerable to cache-collision timing 
     *    attacks unlike AES
     * 3. Since the nonce is not required to be random. There is
     *    no overhead for generating cryptographically secured
     *    pseudo random number
     *
     */
    
    public class CryptoChaCha20 {
    
      private static final String ENCRYPT_ALGO = "ChaCha20-Poly1305/None/NoPadding";
    
      private static final int KEY_LEN = 256;
    
      private static final int NONCE_LEN = 12; //bytes
    
      private static final BigInteger NONCE_MIN_VAL = new BigInteger("100000000000000000000000", 16);
      private static final BigInteger NONCE_MAX_VAL = new BigInteger("ffffffffffffffffffffffff", 16);
    
      private static BigInteger nonceCounter = NONCE_MIN_VAL;
    
      public static byte[] encrypt(byte[] input, SecretKeySpec key)
          throws Exception {
        Objects.requireNonNull(input, "Input message cannot be null");
        Objects.requireNonNull(key, "key cannot be null");
    
        if (input.length == 0) {
          throw new IllegalArgumentException("Length of message cannot be 0");
        }
    
        if (key.getEncoded().length * 8 != KEY_LEN) {
          throw new IllegalArgumentException("Size of key must be 256 bits");
        }
    
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    
        byte[] nonce = getNonce();
    
        IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
    
        cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
    
        byte[] messageCipher = cipher.doFinal(input);
    
        // Prepend the nonce with the message cipher
        byte[] cipherText = new byte[messageCipher.length + NONCE_LEN];
        System.arraycopy(nonce, 0, cipherText, 0, NONCE_LEN);
        System.arraycopy(messageCipher, 0, cipherText, NONCE_LEN,
            messageCipher.length);
        return cipherText;
      }
    
      public static byte[] decrypt(byte[] input, SecretKeySpec key)
          throws Exception {
        Objects.requireNonNull(input, "Input message cannot be null");
        Objects.requireNonNull(key, "key cannot be null");
    
        if (input.length == 0) {
          throw new IllegalArgumentException("Input array cannot be empty");
        }
    
        byte[] nonce = new byte[NONCE_LEN];
        System.arraycopy(input, 0, nonce, 0, NONCE_LEN);
    
        byte[] messageCipher = new byte[input.length - NONCE_LEN];
        System.arraycopy(input, NONCE_LEN, messageCipher, 0, input.length - NONCE_LEN);
    
        IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
    
        Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
        cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
    
        return cipher.doFinal(messageCipher);
      }
    
    
      /**
       * 
       * This method creates the 96 bit nonce. A 96 bit nonce 
       * is required for ChaCha20-Poly1305. The nonce is not 
       * a secret. The only requirement being it has to be 
       * unique for a given key. The following function implements 
       * a 96 bit counter which when invoked always increments 
       * the counter by one.
       * 
       * @return
       */
      public static byte[] getNonce() {
        if (nonceCounter.compareTo(NONCE_MAX_VAL) == -1) {
          return nonceCounter.add(BigInteger.ONE).toByteArray();
        } else {
          nonceCounter = NONCE_MIN_VAL;
          return NONCE_MIN_VAL.toByteArray();
        }
      }
      /**
       * 
       * Strings should not be used to hold the clear text message or the key, as
       * Strings go in the String pool and they will show up in a heap dump. For the
       * same reason, the client calling these encryption or decryption methods
       * should clear all the variables or arrays holding the message or the key
       * after they are no longer needed. Since Java 8 does not provide an easy
       * mechanism to clear the key from {@code SecretKeySpec}, this method uses
       * reflection to clear the key
       * 
       * @param key
       *          The secret key used to do the encryption
       * @throws IllegalArgumentException
       * @throws IllegalAccessException
       * @throws NoSuchFieldException
       * @throws SecurityException
       */
      @SuppressWarnings("unused")
      public static void clearSecret(Destroyable key)
          throws IllegalArgumentException, IllegalAccessException,
          NoSuchFieldException, SecurityException {
        Field keyField = key.getClass().getDeclaredField("key");
        keyField.setAccessible(true);
        byte[] encodedKey = (byte[]) keyField.get(key);
        Arrays.fill(encodedKey, Byte.MIN_VALUE);
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

而且,这是一个 JUnit 测试:

package com.sapbasu.javastudy;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.security.SecureRandom;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.junit.jupiter.api.Test;

public class CryptoChaCha20Test {

  private int KEY_LEN = 256; // bits

  @Test
  public void whenDecryptCalled_givenEncryptedTest_returnsDecryptedBytes()
      throws Exception {

    char[] input = {'e', 'n', 'c', 'r', 'y', 'p', 't', 'i', 'o', 'n'};
    byte[] inputBytes = convertInputToBytes(input);

    KeyGenerator keyGen = KeyGenerator.getInstance("ChaCha20");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();

    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "ChaCha20");
    CryptoChaCha20.clearSecret(secretKey);

    byte[] encryptedBytes = CryptoChaCha20.encrypt(inputBytes, secretKeySpec);
    byte[] decryptedBytes = CryptoChaCha20.decrypt(encryptedBytes, secretKeySpec);

    CryptoChaCha20.clearSecret(secretKeySpec);

    assertArrayEquals(inputBytes, decryptedBytes);

  }

  private byte[] convertInputToBytes(char[] input) {
    CharBuffer charBuf = CharBuffer.wrap(input);
    ByteBuffer byteBuf = Charset.forName(Charset.defaultCharset().name())
        .encode(charBuf);
    byte[] inputBytes = byteBuf.array();
    charBuf.clear();
    byteBuf.clear();
    return inputBytes;
  }
}
Run Code Online (Sandbox Code Playgroud)