Sunday, May 24, 2009

Java Encryption

Update. DES is not secure. Use AES for symmetric encryption.

Most important: do not save keys and data in same location, otherwise don't bother encrypting at all. If the person has access to the key and the data obviously they can just unencrypt.

Consider key storage devises such as HSMs.

------

Ran into various issues implementing Java Encryption so just making some notes here for anyone else facing the same problems.

JCE (Java Cryptography Extension) Cipher Specifics
First of all, need to understand the encryption algorithm used and the specific requirements for using it in conjunction with the JCE (Java Cryptography Extension). For example if you're using DES algorithm to encrypt your data then JCE requires that you initialize the cipher using the IvParameterSpec class. The following post from sun Java forums was helpful in explaining some of this in conjunction with some information from various Java encryption books:

DES Java Encryption

- Cryptographics algorithms come in two flavors, asymmetric (public+private key) and symmetric (secret key).

[Note: Symmetric encryption requires that you keep your key secret because both encryption and decryption are done using the same key. Anyone who has your key can decrypt your data. Asymmetric encryption allows sending the public key to the person who wants to send you encrypted data and using a private key to decrypt the data. This latter form of encryption is useful when you want to allow web visitors, for instance, to encrypt and send you data, but you don't want anyone but yourself to be able to decrypt it - using your private or secret key]

- DES that is a symmetric algorithm.
- Symmetric algorithms come in two principal types, stream ciphers and block ciphers.
- Block ciphers encrypt data in blocks (usually 8-byte or 16-byte blocks).
- DES is a block cipher.
- Block ciphers are used in modes of operation, like ECB, CBC, OFB, etc.
- CBC requires a key and a Initialization Vector


[
Note:
Using JCE an initialization vector provided through IvParameterSpec object.

If you don't provide it you'll get the error: java.security.InvalidKeyException: Parameters missing

If you're using EBC mode it doesn't require an initialization vector and if you pass one in you may ge this error:

java.security.InvalidAlgorithmParameterException: ECB mode cannot use IV

To fix the above error change: "DES/ECB/PKCS5Padding" to "DES/CBC/PKCS5Padding" if EBC is not a requirement. ECB is prone to replay attacks according to some sources.
]

- Initialization vectors are usually random numbers, transmitted in clear, but can be made fixed if your problem requires it.
- If you can have random initialization vectors, you can avoid replay attacks.
[i.e. someone cannot re-use your key unless they have the initialization vector]

Error: java.security.InvalidKeyException: Parameters missing

If using DES this is probably caused by not providing the initialization vector using the IvParameterSpec object.

To use the IvParameterSpec object you need to pass in a parameter which in this case is an array of 8 bytes. This is not a good example because it would be obvious to guess (1, 2, 3, 4... like password 12345) but explains the concept:

byte[] iv = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
IvParameterSpec ivSpec = new IvParameterSpec(iv);

//initialize the cipher using the IvParameterSpec
cipher.init(Cipher.DECRYPT_MODE, key, iv);

DES Key Size

I also found an example of some code which specifies the key size for DES which may prevent errors related to key being too short (not sure about this one but it worked):

String x = "DES/CBC/PKCS5Padding";
KeyGenerator gen = KeyGenerator.getInstance("DES");
gen.init(56); // 56 is the keysize. Fixed for DES
SecretKey key = gen.generateKey();

SecretKey

I did read somewhere that a secret key is problematic because it stores the key in a file somewhere. Will need to do more research on that later. There may be a better alternative than the SecretKey class.

SecureRandom

Initializing the keyGenerator with SecureRandom will help ensure randomness of keys. Some encryption mechanisms are weakened by the ability to guess or obtain keys using brute force because generation of keys uses a pattern that is easy to crack.

gen.init(new SecureRandom());

or for the example above:

gen.init(56, new SecureRandom());

Cross platform encoding of input data - UTF-8:

When getting the bytes of text you are trying to encode you'll want to ensure you're getting them in a format understood by all the systems using or displaying the data. UTF-8 is a good choice for web applications and email. Encode the text you want to encrypt like this:

String stringToEncrypt="encrypt this stuff";

instead of:
byte[] b = stringToEncrypt.getBytes();

use:
byte[] b = stringToEncrypt.getBytes("UTF-8");

javax.crypto.BadPaddingException: Given final block not properly padded

Some encryption algorithms have padding and others do not. The padding will tack on extra characters at the end of something that is encrypted to make it a fixed length. For instance:

test

may become

test===

The === are used to make test 8 characters long in the example above if that is the length I want according to the specification of the algorithm I'm using. The encryption algorithms will probably actually not have equal signs but nulls or some other means of creating a fixed length value.

You'll want to understand how padding is or is not used with your particular encryption algorithm and that padding is handled appropriately. Make sure you are using the correct algorithms, modes, etc. to encrypt and decrypt and that everything you're using matches up.

Additionally if you are transporting or persisting data you may need to encode it so it doesn't get altered in transit. Padding may be trimmed. Some modes of transport (passing data through HTTP via URLs) or persisting data to a database that doesn't understand all the characters in the encrypted bytes may cause characters to be lost or altered. Trying to put encryption data into a String without proper encoding won't work in most cases.

Base64 Encoding encrypted bytes before transported or stored will encode the encryption characters into characters that most systems understand. If you encode to Base64 for transport remember to decode again before you decrypt. Also note that encoding is not the same as encryption. If you encode your data using Base64 anyone can decode it using Base64.

To use Base64 you can download Apache Commons Codec from Apache.org

Import the package:

import org.apache.commons.codec.binary.*;

Code snippet:

Base64 b64 = new Base64();

byte[] a = "encrypt me".getBytes("UTF-8");
byte[] b = b64.encode(a);
encrypt(b) // call your encryption method

Before decrypting:

Byte[] c = b64.decode(b);
decrypt(c); // call your decryption method

Saving Keys to Database

Keys can be saved to a Java keystore which is basically a file. However you may also want to save your keys to a database if you have many keys and performance may be an issue or for other reasons. Remember if anyone gets a hold of your keys they can decrypt all your data so you'll want to think about how to do this securely. Using an encryption algorithm that also requires an initialization vector in addition to your key is helpful because the keys alone will not allow someone to decrypt the data. You may also limit use of keys for a short time period or use some other method to ensure keys are not reused in unintended ways.

If you want to save keys to a database then you can save your keys in VarBinary data type since that will store your byte data in the size it was passed in (no extra padding). Once you create a key you use the .getEncoded() method to get the encoded key bytes and save them to the database. Since the key is already encoded you shouldn't need to further encode it using Base64. Then you can retrieve it from the database using the getBytes("fieldname") Java sql method use it to decrypt your data.

Java Security by Scott Oaks has an example of implementation of a KeyStoreSpi class to use a database with a keystore for key management.