Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
234 views
in Technique[技术] by (71.8m points)

android - Encrypt and decrypt string using ChaCha20

I want to decrypt and encrypt a string using chacha20

BouncyCastleProvider is using chacha20 technique. So I included it jar. and tried the code but not able to work.

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

}

But it is not giving me proper result..

enter image description here

Edit and Updated Code

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

Now I have only problem decrypting. It shows an error that key must be 128 or 256 bits. What am I doing wrong.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Update on 24-DEC-2019 (Correction)

Unlike some other modes in AES like CBC, GCM mode does not require the IV to be unpredictable (same as Chacha20-Poly1305). The only requirement is that the IV (for AES) or nonce (for Chacha20-Poly1305) has to be unique for each invocation with a given key. If it repeats once for a given key, security can be compromised. An easy way to achieve this with good probability is to use a random IV or nonce from a strong pseudo random number generator as shown below. Probability of an IV or nonce collision (assuming a strong random source) will be at most 2^-32, which is low enough to deter attackers.

Using a sequence or timestamp as IV or nonce is also possible, but it may not be as trivial as it may sound. For example, if the system does not correctly keep track of the sequences already used as IV in a persistent store, an invocation may repeat an IV after a system reboot. Likewise, there is no perfect clock. Computer clocks readjusts etc.

Also, the key should be rotated after every 2^32 invocations.

SecureRandom.getInstanceStrong() can be used to generate a cryptographically strong random nonce.


Original Answer

Now that ChaCha20 is supported is Java 11. Here is a sample program for encrypting and decrypting using ChaCha20-Poly1305.

The possible reasons for using ChaCha20-Poly1305 (which is a stream cipher based authenticated encryption algorithm) over AES-GCM (which is an authenticated block cipher algorithm) are:

  1. ChaCha20-Poly1305 is almost 3 times faster than AES when the CPU does not provide dedicated AES instructions. Intel processors provide AES-NI instruction set [1]

  2. ChaCha20-Poly1305 does not need the nonce to be unpredictable / random unlike the IV of AES-GCM. Thus the overhead for running pseudo random number generator can be avoided [2]

  3. ChaCha20 is not vulnerable to cache-collision timing attacks unlike 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);
      }
    }
    

And, here is a JUnit test:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...