【问题标题】:Encrypt and decrypt string using ChaCha20使用 ChaCha20 加密和解密字符串
【发布时间】:2016-06-24 07:04:19
【问题描述】:

我想使用 chacha20

对字符串进行解密和加密

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

PBE.java

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);
    }

}

但这并没有给我正确的结果..

编辑和更新代码

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;
    }
}

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

【问题讨论】:

  • 您的代码明确使用 AES:AES/CTR/NOPADDING 我看不出您在哪里选择 ChaCha20 作为密码。
  • 除了 ChaCha20 是一个简洁的名称之外,您为什么选择它而不是 AES(您实际使用的),这不是一个好的算法。我个人喜欢 BLowFish,因为它是一个很棒的加密算法名称。 ;-)
  • Robert 我应该在哪里指定 chacha20。据我所知,bouncycastle 默认在 chacha20 中加密。如果我错了,请纠正我并指导我如何改正

标签: android encryption bouncycastle


【解决方案1】:

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

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

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

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

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


原答案

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

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

  1. 在 CPU 不提供专用 AES 指令时,ChaCha20-Poly1305 的速度几乎是 AES 的 3 倍。 Intel处理器提供AES-NI指令集[1]

  2. ChaCha20-Poly1305 不像 AES-GCM 的 IV 那样需要 nonce 不可预测/随机。因此可以避免运行伪随机数生成器的开销[2]

  3. ChaCha20 不像 AES [1] 那样容易受到缓存冲突定时攻击

    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);
      }
    }
    

还有,这是一个 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;
  }
}

【讨论】:

  • 这个答案工作正常,应该被标记为正确。
  • 你在 Android Studio 中使用过这段代码吗?
【解决方案2】:

密码的输出由随机位组成(通常由实现限制为 8 位字节)。随机字节可能包含任何字符集中的无效字符。 如果您需要字符串,请将密文编码为 base 64。

此外,您在解密时重新生成 IV。加密/解密过程中的IV应该匹配。

【讨论】:

  • Maarten Bodewes 我试过了。然后加密工作但解密不起作用。并请分享一些 chacha20 在 android 中加密的示例
  • @Nepster 我已经说明了为什么会出现当前错误。抱歉,使用上面的代码示例实现 ChaCha 很棘手,从 AES 转换为 ChaCha 并不能在这里回答问题,它正在为你工作。
  • Maarten 你能指导我一些教程或起点,我可以从哪里学习它。感谢您的帮助
  • @Nepster 你好,Nepster。不幸的是,我自己在围绕 Bouncy Castle 的错误进行编程时遇到了麻烦(相信我,有很多)。此外,没有可用于 ChaCha 的 PBE 实例,因此无论如何您都必须跳过很多圈。通常我什至不会执行这些步骤,但我对 ChaCha 在 Bouncy 中的工作方式很感兴趣。而且我完全没时间了。
猜你喜欢
  • 1970-01-01
  • 2022-11-25
  • 2012-07-01
  • 2012-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多