Encrypt and decrypt string using ChaCha20
Asked Answered
R

2

2

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.

Rhonarhonchus answered 24/6, 2016 at 7:4 Comment(3)
Your code is explicitly using AES: AES/CTR/NOPADDING I don't see where you select ChaCha20 as cipher.Brownie
Other than ChaCha20 being a neat name, why have you chosen it over AES (which you are actually using), not that is is not a good algorithm. Personally I like BLowFish for a great encryption algorithm name. ;-)Chare
Robert where should I specify chacha20. As far as I know bouncycastle is encrypting in chacha20 by default. Correct me if I am wrong and guide me how to make it rightRhonarhonchus
E
11

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;
  }
}
Everything answered 27/1, 2019 at 8:21 Comment(3)
This answer works fine and should have been marked as correct.Laband
did you used this code in Android Studio ?Profligate
I'm getting this error Caused by: java.security.NoSuchAlgorithmException: No provider found for ChaCha20-Poly1305/None/NoPadding at javax.crypto.Cipher.createCipher(Cipher.java:736) at javax.crypto.Cipher.getInstance(Cipher.java:619) at com.tato.app.util.crypto.CryptoChaCha20.encrypt(CryptoChaCha20.java:54)Boohoo
M
2

The output of a cipher consists of random bits (generally limited by implementations to 8-bit bytes). Random bytes are likely to contain invalid characters in any character set. If you require a String, encode the ciphertext to base 64.

Furthermore, you re-generate the IV on decrypt. IV during encryption/decryption should match.

Marcos answered 24/6, 2016 at 18:25 Comment(4)
Maarten Bodewes I have tried. Encrypting is working then But decrypting is not working. And kindly share some sample for chacha20 encryption in androidRhonarhonchus
@Nepster I've indicated why you get the current error. Sorry, implementing ChaCha using above code sample is tricky, and translating from AES to ChaCha is not answering questions here, it's doing work for you.Marcos
Maarten can you guide me some tutorial or starting point from where I can learn it. And thanks for your helpRhonarhonchus
@Nepster Hi Nepster. Unfortunately I have trouble programming around the bugs of Bouncy Castle myself (trust me, there are quite a few). Furthermore, there is no PBE instance available for ChaCha, so you'd have to jump through quite a few hoops anyway. Normally I would not even go through these steps, but I got intrigued in how ChaCha works in Bouncy. And I'm strictly out of time.Marcos

© 2022 - 2024 — McMap. All rights reserved.