10

I want to convert String to secretKey

public void generateCode(String keyStr){ 
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
// Generate the secret key specs.
secretKey skey=keyStr;  //How can I make the casting here
//SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
}

I try to use BASE64Decoder instead of secretKey, but I face a problem which is I cannot specify key length.

EDIT: I want to call this function from another place

 static public String encrypt(String message , String key , int keyLength) throws Exception {
     // Get the KeyGenerator
   KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(keyLength); // 192 and 256 bits may not be available
    // Generate the secret key specs.
     SecretKey skey = key; //here is the error
   byte[] raw = skey.getEncoded();
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    // Instantiate the cipher
    Cipher cipher = Cipher.getInstance("AES");

    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    System.out.println("msg is" + message + "\n raw is" + raw);
    byte[] encrypted = cipher.doFinal(message.getBytes());
    String cryptedValue = new String(encrypted);
    System.out.println("encrypted string: " + cryptedValue);
    return cryptedValue;
}

If anybody could help, I'd be very thankful.

Daryl Bennett
  • 462
  • 10
  • 22
palAlaa
  • 9,500
  • 33
  • 107
  • 166
  • The `KeyGenerator kgen` is never used. What is that doing there? A `KeyGenerator` is used to choose a new random key. But it looks like you want to reuse an existing key that is somehow stored in a `String` (Base-64 encoded, perhaps?) – erickson Jan 11 '12 at 04:02

2 Answers2

40

No integrity checks, for these particular reasons

  1. The need is not apparent from the use case.
  2. "AES/GCM/NoPadding" mode is only available from Java 7 onward
  3. It depends on the user if they want to deploy e.g. HMAC and/or AESCMAC (recommended).
  4. It would require an additional key at the minimum, and two full passes.

If you got an implementation of GCM mode at both sides - e.g. using Bouncy Castle on Java 6 - please go for it, as it is much more secure (as long as the "IV" is really unique). It should be really easy to change the implementation.

Implementation notes regarding encryption

  1. This implementation is not safe when used in an unrestricted client / server role because of padding oracle attacks (they require 128 tries per byte or lower, on average, independent of algorithm or key size). You will need to use a MAC, HMAC or Signature over the encrypted data, and verify it before decrypting to deploy it in client/server mode.
  2. Decrypt will return null if decryption fails. This can only indicate a padding exception, which should be adequately handled (did I warn about padding oracle attacks?)
  3. Invalid keys will be returned as InvalidArgumentException.
  4. All other security related exceptions are "swept under the table" as it means that the Java runtime environment is invalid. For example, supporting "UTF-8" and "AES/CBC/PKCS5Padding" is required for every Java SE implementation.

Some other notes

  1. Please don't try the opposite and insert bytes directly into the input string of the encrypt method (using new String(byte[]) for instance). The method may fail silently!
  2. Optimized for readability. Go for Base64 stream and CipherStream implementations if you rather prefer speed and better memory footprint.
  3. You need at least Java 6 SE or compatible to run this code.
  4. Encryption/decryption may fail for AES key sizes over 128 bit as you may need policy files for unrestricted encryption (available from Oracle)
  5. Beware of governmental regulations when exporting encryption.
  6. This implementation uses hex keys instead of base64 keys as they are small enough, and hex is just easier to edit/verify manually.
  7. Used hex and base64 encoding/decoding retrieved from the JDK, no external libraries needed whatsoever.
  8. Uber simple to use, but of course not very object oriented, no caching of object instances used in encrypt/decrypt. Refactor at will.

OK, here comes some code...

    public static String encrypt(final String plainMessage,
            final String symKeyHex) {
        final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);

        final byte[] encodedMessage = plainMessage.getBytes(Charset
                .forName("UTF-8"));
        try {
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            final int blockSize = cipher.getBlockSize();

            // create the key
            final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

            // generate random IV using block size (possibly create a method for
            // this)
            final byte[] ivData = new byte[blockSize];
            final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
            rnd.nextBytes(ivData);
            final IvParameterSpec iv = new IvParameterSpec(ivData);

            cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);

            final byte[] encryptedMessage = cipher.doFinal(encodedMessage);

            // concatenate IV and encrypted message
            final byte[] ivAndEncryptedMessage = new byte[ivData.length
                    + encryptedMessage.length];
            System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
            System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
                    blockSize, encryptedMessage.length);

            final String ivAndEncryptedMessageBase64 = DatatypeConverter
                    .printBase64Binary(ivAndEncryptedMessage);

            return ivAndEncryptedMessageBase64;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(
                    "key argument does not contain a valid AES key");
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException(
                    "Unexpected exception during encryption", e);
        }
    }

    public static String decrypt(final String ivAndEncryptedMessageBase64,
            final String symKeyHex) {
        final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);

        final byte[] ivAndEncryptedMessage = DatatypeConverter
                .parseBase64Binary(ivAndEncryptedMessageBase64);
        try {
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            final int blockSize = cipher.getBlockSize();

            // create the key
            final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

            // retrieve random IV from start of the received message
            final byte[] ivData = new byte[blockSize];
            System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
            final IvParameterSpec iv = new IvParameterSpec(ivData);

            // retrieve the encrypted message itself
            final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length
                    - blockSize];
            System.arraycopy(ivAndEncryptedMessage, blockSize,
                    encryptedMessage, 0, encryptedMessage.length);

            cipher.init(Cipher.DECRYPT_MODE, symKey, iv);

            final byte[] encodedMessage = cipher.doFinal(encryptedMessage);

            // concatenate IV and encrypted message
            final String message = new String(encodedMessage,
                    Charset.forName("UTF-8"));

            return message;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(
                    "key argument does not contain a valid AES key");
        } catch (BadPaddingException e) {
            // you'd better know about padding oracle attacks
            return null;
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException(
                    "Unexpected exception during decryption", e);
        }
    }

Usage:

    String plain = "Zaphod's just zis guy, ya knöw?";
    String encrypted = encrypt(plain, "000102030405060708090A0B0C0D0E0F");
    System.out.println(encrypted);
    String decrypted = decrypt(encrypted, "000102030405060708090A0B0C0D0E0F");
    if (decrypted != null && decrypted.equals(plain)) {
        System.out.println("Hey! " + decrypted);
    } else {
        System.out.println("Bummer!");
    }
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • 2
    To use `"AES/GCM/NoPadding"` mode encryption in Java 7, replace the code for the `IvParameterSpec` with ['GCMParameterSpec params = new GCMParameterSpec(blockSize * Byte.SIZE, ivData);'](http://docs.oracle.com/javase/7/docs/api/javax/crypto/spec/GCMParameterSpec.html) and don't forget to handle the [`AEADBadTagException`](http://docs.oracle.com/javase/7/docs/api/javax/crypto`/AEADBadTagException.html) to catch integrity exceptions. Bouncy will work by simply using `"AES/GCM/NoPadding"` and will throw a BadPaddingException, returning null. – Maarten Bodewes Jan 12 '12 at 00:34
  • Ah, AEADBadTagException actually extends BadPaddingException, so the exception handling should still be ok. – Maarten Bodewes Jan 12 '12 at 01:32
  • 1
    @YuDroid It's just a (base 64) encoder / decoder. Up to not long ago, the base API does not do base 64 or hexadecimal encoding / decoding, and even now it's just part of the optional `javax.xml.bind` package. You can use the [Base64 class included in Android](http://developer.android.com/reference/android/util/Base64.html) instead. I went for this one because I did not want the code to rely on additional libraries. – Maarten Bodewes Feb 18 '14 at 18:40
  • @owlstead Can you provide an example with using Base64 from the Android API? I am not sure which methods to use to substitute the DatatypeConverter methods. – Catalin Morosan Jul 17 '14 at 14:50
  • I'd rather not *here* as that would make the code too complex. I don't know what you're missing from my previous comment above, but if you cannot work it out please don't hesitate to ask a new question on OS. – Maarten Bodewes Jul 17 '14 at 14:55
  • 1
    Doesn't work for me. I am replacing this `DatatypeConverter.parseHexBinary(symKeyHex)` with Base64.encode(symKeyHex.getBytes(), Base64.DEFAULT); and other DatatypeConverter things but its giving me exception **java.lang.IllegalArgumentException: key argument does not contain a valid AES key** what I am doing wrong. Thnaks – Zeeshan Aug 12 '14 at 06:02
  • @ShanXeeshi `parse` parses hex and `encode` goes the other way? – Maarten Bodewes Aug 12 '14 at 06:56
  • Sorry, didn't get you – Zeeshan Aug 12 '14 at 07:09
  • 1
    @owlstead would you please post an example somewhere how to change `DatatypeConverter.parseHexBinary(symKeyHex)` and other `DataTypeConverter` functions to `Base64`? Hint you gave may seem obvious to you but I am a newbie here and not able to get the point out of that comment. :( – Darpan Aug 23 '14 at 14:30
4

Here's the version using Base64 Util class instead of DatatypeConverter

public static String encrypt(final String plainMessage,
                             final String symKeyHex) {
    final byte[] symKeyData = Base64.decode(symKeyHex,Base64.DEFAULT);

    final byte[] encodedMessage = plainMessage.getBytes(Charset
            .forName("UTF-8"));
    try {
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        final int blockSize = cipher.getBlockSize();

        // create the key
        final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

        // generate random IV using block size (possibly create a method for
        // this)
        final byte[] ivData = new byte[blockSize];
        final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
        rnd.nextBytes(ivData);
        final IvParameterSpec iv = new IvParameterSpec(ivData);

        cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);

        final byte[] encryptedMessage = cipher.doFinal(encodedMessage);

        // concatenate IV and encrypted message
        final byte[] ivAndEncryptedMessage = new byte[ivData.length
                + encryptedMessage.length];
        System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
        System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
                blockSize, encryptedMessage.length);

        final String ivAndEncryptedMessageBase64 = Base64.encodeToString(ivAndEncryptedMessage,Base64.DEFAULT);

        return ivAndEncryptedMessageBase64;
    } catch (InvalidKeyException e) {
        throw new IllegalArgumentException(
                "key argument does not contain a valid AES key");
    } catch (GeneralSecurityException e) {
        throw new IllegalStateException(
                "Unexpected exception during encryption", e);
    }
}

public static String decrypt(final String ivAndEncryptedMessageBase64,
                             final String symKeyHex) {
    final byte[] symKeyData = Base64.decode((symKeyHex),Base64.DEFAULT);

    final byte[] ivAndEncryptedMessage = Base64.decode(ivAndEncryptedMessageBase64,Base64.DEFAULT);
    try {
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        final int blockSize = cipher.getBlockSize();

        // create the key
        final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

        // retrieve random IV from start of the received message
        final byte[] ivData = new byte[blockSize];
        System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
        final IvParameterSpec iv = new IvParameterSpec(ivData);

        // retrieve the encrypted message itself
        final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length
                - blockSize];
        System.arraycopy(ivAndEncryptedMessage, blockSize,
                encryptedMessage, 0, encryptedMessage.length);

        cipher.init(Cipher.DECRYPT_MODE, symKey, iv);

        final byte[] encodedMessage = cipher.doFinal(encryptedMessage);

        // concatenate IV and encrypted message
        final String message = new String(encodedMessage,
                Charset.forName("UTF-8"));

        return message;
    } catch (InvalidKeyException e) {
        throw new IllegalArgumentException(
                "key argument does not contain a valid AES key");
    } catch (BadPaddingException e) {
        // you'd better know about padding oracle attacks
        return null;
    } catch (GeneralSecurityException e) {
        throw new IllegalStateException(
                "Unexpected exception during decryption", e);
    }
}

Just a reminder for those who get a Padding exception. Make sure you are using the correct Key length. Hint: look at Maarten's post: his hex is exactly 32 ;) That's no coincidence :)

Jordy
  • 1,764
  • 1
  • 22
  • 32