8

I've created this two extensions in Kotlin to Encrypt/Decrypt strings:

fun String.encrypt(seed : String): String {
    val keyGenerator = KeyGenerator.getInstance("AES")
    val secureRandom = SecureRandom.getInstance("SHA1PRNG")
    secureRandom.setSeed(seed.toByteArray())

    keyGenerator.init(128, secureRandom)
    val skey = keyGenerator.generateKey()
    val rawKey : ByteArray = skey.encoded

    val skeySpec = SecretKeySpec(rawKey, "AES")
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec)
    val byteArray = cipher.doFinal(this.toByteArray())

    return byteArray.toString()
}

fun String.decrypt(seed : String): String {
    val keyGenerator = KeyGenerator.getInstance("AES")
    val secureRandom = SecureRandom.getInstance("SHA1PRNG")
    secureRandom.setSeed(seed.toByteArray())

    keyGenerator.init(128, secureRandom)
    val skey = keyGenerator.generateKey()
    val rawKey : ByteArray = skey.encoded

    val skeySpec = SecretKeySpec(rawKey, "AES")
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.DECRYPT_MODE, skeySpec)
    val byteArray = cipher.doFinal(this.toByteArray())

    return byteArray.toString()
}

for some reason I'm getting the following exception:

javax.crypto.IllegalBlockSizeException: last block incomplete in decryption

What I'm doing wrong?

MarcForn
  • 3,321
  • 7
  • 25
  • 39
  • 1
    Don't define your cipher as just "AES". Use "AES/CBC/PKCS5Padding". You'll also need to add an IV. – Luke Joshua Park Mar 17 '18 at 17:49
  • 1
    Besides that, using the `SecureRandom` class to *derive* keys is absolutely tosh. SHA1PRNG isn't well defined and may produce different values *including completely random values* even if the seed is identical. See all the info about `getRawKey` trap. – Maarten Bodewes Mar 17 '18 at 18:03
  • See my answer [here](https://stackoverflow.com/a/11420318/589259) – Maarten Bodewes Mar 17 '18 at 18:07

6 Answers6

20

AES Encryption / Decryption using base64 key, salt and iv (Initialization Vector).

object AESEncyption {

const val secretKey = "tK5UTui+DPh8lIlBxya5XVsmeDCoUl6vHhdIESMB6sQ="
const val salt = "QWlGNHNhMTJTQWZ2bGhpV3U=" // base64 decode => AiF4sa12SAfvlhiWu
const val iv = "bVQzNFNhRkQ1Njc4UUFaWA==" // base64 decode => mT34SaFD5678QAZX

fun encrypt(strToEncrypt: String) :  String?
{
    try
    {
        val ivParameterSpec = IvParameterSpec(Base64.decode(iv, Base64.DEFAULT))

        val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
        val spec =  PBEKeySpec(secretKey.toCharArray(), Base64.decode(salt, Base64.DEFAULT), 10000, 256)
        val tmp = factory.generateSecret(spec)
        val secretKey =  SecretKeySpec(tmp.encoded, "AES")

        val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
        return Base64.encodeToString(cipher.doFinal(strToEncrypt.toByteArray(Charsets.UTF_8)), Base64.DEFAULT)
    }
    catch (e: Exception)
    {
        println("Error while encrypting: $e")
    }
    return null
}

fun decrypt(strToDecrypt : String) : String? {
    try
    {

        val ivParameterSpec =  IvParameterSpec(Base64.decode(iv, Base64.DEFAULT))

        val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
        val spec =  PBEKeySpec(secretKey.toCharArray(), Base64.decode(salt, Base64.DEFAULT), 10000, 256)
        val tmp = factory.generateSecret(spec);
        val secretKey =  SecretKeySpec(tmp.encoded, "AES")

        val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        return  String(cipher.doFinal(Base64.decode(strToDecrypt, Base64.DEFAULT)))
    }
    catch (e : Exception) {
        println("Error while decrypting: $e");
    }
    return null
  }
}

iOS swift

Kasım Özdemir
  • 5,414
  • 3
  • 18
  • 35
9

Following Maarten Bodews guides I fix the issues as:

fun String.encrypt(password: String): String {
    val secretKeySpec = SecretKeySpec(password.toByteArray(), "AES")
    val iv = ByteArray(16)
    val charArray = password.toCharArray()
    for (i in 0 until charArray.size){
        iv[i] = charArray[i].toByte()
    }
    val ivParameterSpec = IvParameterSpec(iv)

    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)

    val encryptedValue = cipher.doFinal(this.toByteArray())
    return Base64.encodeToString(encryptedValue, Base64.DEFAULT)
}

fun String.decrypt(password: String): String {
    val secretKeySpec = SecretKeySpec(password.toByteArray(), "AES")
    val iv = ByteArray(16)
    val charArray = password.toCharArray()
    for (i in 0 until charArray.size){
        iv[i] = charArray[i].toByte()
    }
    val ivParameterSpec = IvParameterSpec(iv)

    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)

    val decryptedByteValue = cipher.doFinal(Base64.decode(this, Base64.DEFAULT))
    return String(decryptedByteValue)
}
MarcForn
  • 3,321
  • 7
  • 25
  • 39
  • 11
    You may want to lookup PBKDF2 as explained in my answer. Using the password as IV means you cannot encrypt more than one message with the same password. You may want to read into cryptography before continuing, or use a ready made solution. **Warning** above code is *not* secure. – Maarten Bodewes Mar 17 '18 at 18:56
  • The above comment can easily be solved by not using the password as IV. However, I get Unresolved reference: encodeToString and Unresolved reference: DEFAULT . What am I missing? – metamonkey Jul 20 '21 at 04:24
7

To encode your ciphertext use base 64 or hexadecimals. The Java API contains a Base64 class, so you're probably best off using that.

byte[]#toString doesn't do what you expect it to do; it simply returns a representation of the byte array reference, not the contents of the byte array.


Besides that:

  • don't use SecureRandom to derive a key, try and lookup PBKDF2;
  • explicitly use a mode of operation such as "AES/GCM/NoPadding";
  • use a unique IV, and a random IV if you decide to use CBC (usually insecure);
  • don't use toByteArray without explicitly selecting a character encoding for the message.
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
4

The easiest way of implementing AES Encryption and Decryption in Android is to copy this class in your projects.

Encrypt Strings

Please copy the AESUtils class in your project first and then you can use it like this.

String encrypted = "";
String sourceStr = "This is any source string";
try {
    encrypted = AESUtils.encrypt(sourceStr);
    Log.d("TEST", "encrypted:" + encrypted);
} catch (Exception e) {
    e.printStackTrace();
}

Decrypt Strings

String encrypted = "ANY_ENCRYPTED_STRING_HERE";
String decrypted = "";
try {
    decrypted = AESUtils.decrypt(encrypted);
    Log.d("TEST", "decrypted:" + decrypted);
} catch (Exception e) {
    e.printStackTrace();
}

AESUtils Class

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

public class AESUtils 
{

    private static final byte[] keyValue =
            new byte[]{'c', 'o', 'd', 'i', 'n', 'g', 'a', 'f', 'f', 'a', 'i', 'r', 's', 'c', 'o', 'm'};


    public static String encrypt(String cleartext)
            throws Exception {
        byte[] rawKey = getRawKey();
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String encrypted)
            throws Exception {

        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(enc);
        return new String(result);
    }

    private static byte[] getRawKey() throws Exception {
        SecretKey key = new SecretKeySpec(keyValue, "AES");
        byte[] raw = key.getEncoded();
        return raw;
    }

    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKey skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] encrypted)
            throws Exception {
        SecretKey skeySpec = new SecretKeySpec(keyValue, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length() / 2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
                    16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2 * buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }

    private final static String HEX = "0123456789ABCDEF";

    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
    }
}
Bolt UIX
  • 5,988
  • 6
  • 31
  • 58
3

You can Encrypt and Decrypt in Kotlin like this

first you need:

val SECRET_KEY = "secretKey"
val SECRET_IV = "secretIV"

after that

Encrypt:

private fun String.encryptCBC(): String {
    val iv = IvParameterSpec(SECRET_IV.toByteArray())
    val keySpec = SecretKeySpec(SECRET_KEY.toByteArray(), "AES")
    val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
    cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv)
    val crypted = cipher.doFinal(this.toByteArray())
    val encodedByte = Base64.encode(crypted, Base64.DEFAULT)
    return String(encodedByte)
}

and Decrypt:

private fun String.decryptCBC(): String {
    val decodedByte: ByteArray = Base64.decode(this, Base64.DEFAULT)
    val iv = IvParameterSpec(SECRET_IV.toByteArray())
    val keySpec = SecretKeySpec(SECRET_KEY.toByteArray(), "AES")
    val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
    cipher.init(Cipher.DECRYPT_MODE, keySpec, iv)
    val output = cipher.doFinal(decodedByte)
    return String(output)
}

after that you can use it like this:

val strEncrypt = edt.text.toString().encryptCBC()
val strDecrypted = strEncrypt?.decryptCBC()
H.Fa8
  • 318
  • 3
  • 10
0

Following MarcForn guide I reduce it like this this:

const val encryptionKey = "ENCRYPTION_KEY"

fun String.cipherEncrypt(encryptionKey: String): String? {
    try {
        val secretKeySpec = SecretKeySpec(encryptionKey.toByteArray(), "AES")
        val iv = encryptionKey.toByteArray()
        val ivParameterSpec = IvParameterSpec(iv)

        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)

        val encryptedValue = cipher.doFinal(this.toByteArray())
        return Base64.encodeToString(encryptedValue, Base64.DEFAULT)
    } catch (e: Exception) {
        e.message?.let{ Log.e("encryptor", it) }
    }
    return null
}

fun String.cipherDecrypt(encryptionKey: String): String? {
    try {
        val secretKeySpec = SecretKeySpec(encryptionKey.toByteArray(), "AES")
        val iv = encryptionKey.toByteArray()
        val ivParameterSpec = IvParameterSpec(iv)

        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)

        val decodedValue = Base64.decode(this, Base64.DEFAULT)
        val decryptedValue = cipher.doFinal(decodedValue)
        return String(decryptedValue)
    } catch (e: Exception) {
        e.message?.let{ Log.e("decryptor", it) }
    }
    return null
}