import javax.crypto.Cipher;
public abstract class Crypto {
private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
private String AesKeyString = "ByWelFHCgFqivFZrWs89LQ==";
private void setKey() throws NoSuchAlgorithmException{
byte[] keyBytes;
keyBytes = Base64.getDecoder().decode(AesKeyString);
aesKey = new SecretKeySpec(keyBytes, "AES");
}
protected byte[] execute(int mode, byte[] target, byte[] iv)
throws Exception{
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(mode, aesKey, ivSpec);
return cipher.doFinal(target);
}
}
According to NIST Recommendation - Appendix B, there are two valid approaches to construct the initial counter blocks (AES is a 128-bit block cipher):
128-bit nonce XORed with an m-bit counter value (usually 32 bits).
64-bit nonce prepended to a 64-bit counter.
My question is:
What is the exact procedure regarding the initial counter block used
in an "AES/CTR/NoPadding" instance of javax.crypto.Cipher (assuming SunJCE as the provider)?
That is, given the above code, which of the previous approaches for the initial counter block is used, if any?
Java simply leaves the choice of the way you construct the counter to you. You simply have to initialize the CTR mode using a 16 byte IV, which is nothing more than the initial counter value.
Once you start encrypting it will use a counter over the full 128 bits. Then again, you would hardly want it to start over as that would directly compromise the security of the plaintext. The disadvantage is that the 32 bit XOR method is not directly supported (if you start with a a counter of FFFFFFFF the next value will alter the 33rd least significant bit of the counter).
Then again, I would rather choose a 8-byte nonce and leave the least significant bits set to all zeros. Or choose GCM mode of course.
Proof:
Cipher aesCTR = Cipher.getInstance("AES/CTR/NoPadding");
SecretKey aesKey = new SecretKeySpec(new byte[16], "AES");
IvParameterSpec lastIV = new IvParameterSpec(Hex.decode("FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF"));
aesCTR.init(Cipher.ENCRYPT_MODE, aesKey, lastIV);
byte[] twoBlocks = aesCTR.doFinal(new byte[2 * aesCTR.getBlockSize()]);
byte[] secondBlock = Arrays.copyOfRange(twoBlocks, 16, 32);
System.out.printf("%s%n", Hex.toHexString(secondBlock));
IvParameterSpec firstIV = new IvParameterSpec(new byte[16]); // all zero IV
aesCTR.init(Cipher.ENCRYPT_MODE, aesKey, firstIV);
byte[] oneBlock = aesCTR.doFinal(new byte[aesCTR.getBlockSize()]);
System.out.printf("%s%n", Hex.toHexString(oneBlock));
Output:
66e94bd4ef8a2c3b884cfa59ca342b2e
66e94bd4ef8a2c3b884cfa59ca342b2e
Related
I need to wrap private value with AESWrap. I have a problem with this operation because of length of my private value string (key to be wrapped).
This is my implementation:
final byte[] kek = // ... generate SHA-256 key via `PBKDF2WithHmacSHA256`
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AESWrap", "SunJCE");
c.init(Cipher.WRAP_MODE, sKey);
byte[] bytes = privateValue.getBytes();
SecretKeySpec wk = new SecretKeySpec(bytes, "AES");
byte[] result = c.wrap(wk);
Since SunJCE provider doesn't support any padding for key wrapping so private value should be multiples of 8 bytes and this is my problem I need to solve.
Question: How to solve this situation when private value has not sufficient length. Is there some recommended way how to do padding on my own?
P.S. I would like to avoid external libraries like BC etc.
With respect to #Maarten's answer I created this implementation. It works (it wraps and unwraps my private value successfully), but is this implementation secure?
Wrapping
byte[] salt = .... // 32 random bytes...
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
c.init(Cipher.WRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
byte[] result = c.wrap(wk); // wrapped private value
Unwrapping
byte[] kek = ... // PBKDF2WithHmacSHA256 hash from private value and previous salt
SecretKey sKey = new SecretKeySpec(kek, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
IvParameterSpec iv = new IvParameterSpec(parsed.getIv()); // previously created iv
c.init(Cipher.UNWRAP_MODE, sKey, iv);
SecretKeySpec wk = new SecretKeySpec(privateValue.getBytes(), "AES");
Key result = c.unwrap(parsed.getKey(), "AES", Cipher.SECRET_KEY);
byte[] pv = result.getEncoded(); // unwrapped private value
It is possible to use a normal mode of operation rather than a specialized padding mode for AES. The padding mode is nicer, but just CBC with PKCS#7 padding should suffice as well.
It would be wise to use an IV and store it with the wrapped key. Private keys are generally not just binary data, but have structure, and you may leak a tiny bit of information if you wrap multiple keys this way. For RSA the randomized modulus comes before the private exponent / CRT parameters so you should be secure with a zero IV as well.
// --- key pair with private key for testing
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(4096);
KeyPair kp = gen.generateKeyPair();
// --- create KEK
final byte[] kek = new byte[16]; // test value
SecretKey sKey = new SecretKeySpec(kek, "AES");
// --- the cipher, not a special wrapping algorithm
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
// --- create IV
// not really necessary because the modulus comes first, but nicer
SecureRandom rng = new SecureRandom();
byte[] ivBytes = new byte[c.getBlockSize()];
rng.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
// --- init & wrap by normal encryption
c.init(Cipher.WRAP_MODE, sKey, iv);
byte[] result = c.wrap(kp.getPrivate());
AES-SIV would be nicer, but that's not included in the SunJCE provider. You could use AES-GCM which ads integrity but beware that repeating the 12 byte IV (nonce) for that can be catastrophic for that mode.
I'm trying to make an encryption-decryption app. I've got two classes - one with functions to generate the key, encrypt and decrypt, second one for JavaFX GUI. In the GUI class I've got 4 textareas: 1st to write text to encrypt, 2nd for encrypted text, 3rd for the key (String encodedKey = Base64.getEncoder().encodeToString(klucz.getEncoded());) and 4th for decrypted text.
The problem is, I am not able to decrypt the text. I'm trying to recreate the SecretKey like this:
String encodedKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz = new SecretKeySpec(decodedKey, "DESede");
When I encrypt the key looks like this: com.sun.crypto.provider.DESedeKey#4f964d80 and when I try to recreate it: javax.crypto.spec.SecretKeySpec#4f964d80 and I'm getting javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher
Here is my 1st class:
public class Encryption {
public static SecretKey generateKey() throws NoSuchAlgorithmException {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
SecretKey klucz = keygen.generateKey();
return klucz;
}
static byte[] encrypt(byte[] plainTextByte, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, klucz);
byte[] encryptedBytes = cipher.doFinal(plainTextByte);
return encryptedBytes;
}
static byte[] decrypt(byte[] encryptedBytes, SecretKey klucz)
throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, klucz);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return decryptedBytes;
}
}
edit
btnEncrypt.setOnAction((ActionEvent event) -> {
try {
String plainText = textAreaToEncrypt.getText();
SecretKey klucz = Encryption.generateKey();
byte[] plainTextByte = plainText.getBytes();
byte[] encryptedBytes = Encryption.encrypt(plainTextByte, klucz);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
textAreaEncryptedText.setText(encryptedText);
byte[] byteKey = klucz.getEncoded();
String stringKey = Base64.getEncoder().encodeToString(byteKey);
textAreaKey.setTextstringKey
} catch (Exception ex) {
ex.printStackTrace();
}
});
btnDecrypt.setOnAction((ActionEvent event) -> {
try {
String stringKey = textAreaKey.getText();
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
SecretKey klucz2 = new SecretKeySpec(decodedKey, "DESede");
String encryptedText = textAreaEncryptedText.getText();
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedText.getBytes());
byte[] decryptedBytes = Encryption.decrypt(encryptedBytes, klucz2;
String decryptedText = Base64.getEncoder().encodeToString(decryptedBytes);
textAreaDecryptedText.setText(decryptedText);
} catch (Exception ex) {
ex.printStackTrace();
}
});
One of your problems is here:
String encryptedText = new String(encryptedBytes, "UTF8");
Generally, many byte sequences in cipher text are not valid UTF-8–encoded characters. When you try to create a String, this malformed sequences will be replaced with the "replacement character", and then information from the the cipher text is irretrievably lost. When you convert the String back to bytes and try to decrypt it, the corrupt cipher text raises an error.
If you need to represent the cipher text as a character string, use base-64 encoding, just as you do for the key.
The other principal problem is that you are aren't specifying the full transformation. You should specify the "mode" and "padding" of the cipher explicitly, like "DESede/ECB/PKCS5Padding".
The correct mode will depend on your assignment. ECB is generally not secure, but more secure modes add a bit of complexity that may be outside the scope of your assignment. Study your instructions and clarify the requirements with your teacher if necessary.
There are two main issues:
You should not use user entered password as a key (there are difference between them). The key must have specific size depending on the cipher (16 or 24 bytes for 3des)
Direct 3DES (DESede) is a block cipher encrypting 8 bytes at once. To encrypt multiple blocks, there are some methods defined how to do that properly. It is calls Block cipher mode.
For proper encryption you need to take care of a few more things
Creating a key from the password
Let's assume you want to use DESede (3des). The key must have fixed size - 16 or 24 bytes. To properly generate a key from password you should use PBKDF. Some people are sensitive to "must use", however neglecting this step really compromises the encryption security mainly using user-entered passwords.
For 3DES you can use :
int keySize = 16*8;
int iterations = 800000;
char[] password = "password".toCharArray();
SecureRandom random = new SecureRandom();
byte[] salt = random.generateSeed(8);
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(spec);
SecretKey desSecret = new SecretKeySpec(pbeSecretKey.getEncoded(), "DESede");
// iv needs to have block size
// we will use the salt for simplification
IvParameterSpec ivParam = new IvParameterSpec(salt);
Cipher cipher = Cipher.getInstance("DESEde/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, desSecret, ivParam);
System.out.println("salt: "+Base64.getEncoder().encodeToString(salt));
System.out.println(cipher.getIV().length+" iv: "+Base64.getEncoder().encodeToString(cipher.getIV()));
byte[] ciphertext = cipher.doFinal("plaintext input".getBytes());
System.out.println("encrypted: "+Base64.getEncoder().encodeToString(ciphertext));
if you can ensure that your password has good entropy (is long and random enough) you may be good with a simple hash
MessageDigest dgst = MessageDigest.getInstance("sha-1");
byte[] hash = dgst.digest("some long, complex and random password".getBytes());
byte[] keyBytes = new byte[keySize/8];
System.arraycopy(hash, 0, keyBytes, 0, keySize/8);
SecretKey desSecret = new SecretKeySpec(keyBytes, "DESede");
The salt serves to randomize the output and should be used.
The output of the encryption should be salt | cipthertext | tag (not necessarily in this order, but you will need all of these for proper encryption).
To decrypt the output, you will need to split the output to salt, ciphertext and the tag.
I see zero vectors ( static salt or iv ) very often in examples from StackOverflow, but in many cases it may lead to broken ciphers revelaling key or plaintext.
The initialization vector iv is needed for block chain modes (encrypting longer input than a single block), we could use the salt from the key as well
when having the same size ( 8 bytes in our case). For really secure solution the password salt should be longer.
The tag is an authentication tag, to ensure that nobody has manipulated with the ciphertext. You could use HMAC of the plaintext or ciphertext. It is important you should use different key for HMAC than for encryption. However - I believe in your case your homework will be ok even without the hmac tag
public static void main(String[] args) throws Exception {
String iv = "0102030405060708";
String key = "1882051051AgVfZUKJLInUbWvOPsAP6LM6nBwLn14140722186";
byte[] aaa = AES_cbc_decrypt("hv208Otx0FZL32GUuErHDLlZzC3zVEGRt56f8lviQpk=", key, iv);
System.out.println(new String(aaa));
}
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
public static byte[] AES_cbc_decrypt(String content,String key,String iv) throws Exception
{
byte[] contentBytes = Base64.decode(content);
byte[] keyBytes = key.substring(0, 16).getBytes();
byte[] ivBytes = iv.getBytes();
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));
byte[] decbbdt = cipher.doFinal(contentBytes);
return decbbdt;
}
run with this code and i get the follow exception :
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
it can be decrypt by php method
openssl_decrypt(base64_decode($encryptData), 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
You try to decrypt with a key of 16 bytes or 128 bits. However, you have been using AES-256 where 256 denotes the key size: 32 bytes of course.
Now C and C-libraries such as OpenSSL generally use pointer arithmetic to determine the amount of bytes. When specifying the key they generally take a pointer address and an amount of bytes (or for lower level libraries, 32 bit words, etc.)
So in all likelihood when specifying a key larger than 32 characters / bytes this key is cut down to 32 bytes (or chars in C, where bytes and characters are for ever confused). However in your Java code you cut down the key to 16 bytes. This would lead to using AES-256 in C and AES-128 in Java.
Moral of the story: don't confuse passwords / strings and keys.
I create an encryption cipher as follows (in Scala, using bouncy-castle)
def encryptCipher(secret:SecretKeySpec, iv:IvParameterSpec):Cipher = {
val e = Cipher.getInstance("AES/GCM/NoPadding")
e.init(Cipher.ENCRYPT_MODE, secret, iv)
}
You see that the slow operation of generating the key spec is already handled. However calling init itself for each message is too slow.
I'm currently processing 50K messages, and calling the init method adds nearly 4 seconds.
Is there a way to re-initialise with a new IV which is not so time intensive?
There's no standard way to do that in the standard library,
but there's a good workaround if you're using AES:
The purpose of the IV is to eliminate the possibility that same plain texts encrypt into the same cipher texts.
You can just "update" (as in Cipher.update(byte[])) with a random block-size byte array before encrypting (and with the same block when decrypting). This is almost exactly the same as using the same random block as IV.
To see that, run this snippet (that uses the above method to generate exactly the same cipher text - but this is just for compatibility with other platforms, there's no need to calculate a specific IV for it to be secure.
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecureRandom secureRandom = new SecureRandom();
byte[] keyBytes = new byte[16];
secureRandom.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
byte[] plain = new byte[256];
secureRandom.nextBytes(plain);
// first init using random IV (save it for later)
cipher.init(Cipher.ENCRYPT_MODE, key, secureRandom);
byte[] realIv = cipher.getIV();
byte[] expected = cipher.doFinal(plain);
// now init using dummy IV and encrypt with real IV prefix
IvParameterSpec nullIv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.ENCRYPT_MODE, key, nullIv);
// calculate equivalent iv
Cipher equivalentIvAsFirstBlock = Cipher.getInstance("AES/CBC/NoPadding");
equivalentIvAsFirstBlock.init(Cipher.DECRYPT_MODE, key, nullIv);
byte[] equivalentIv = equivalentIvAsFirstBlock.doFinal(realIv);
cipher.update(equivalentIv);
byte[] result = cipher.doFinal(plain);
System.out.println(Arrays.equals(expected, result));
The decryption part is easier because the result of the block-decryption is XORed with the previous cipher text (see Block cipher mode of operation), you just need to append the real IV to cipher-text, and throw it afterwards:
// Encrypt as before
IvParameterSpec nullIv = new IvParameterSpec(new byte[16]);
cipher.init(Cipher.DECRYPT_MODE, key, nullIv);
cipher.update(realIv);
byte[] result = cipher.doFinal(encrypted);
// result.length == plain.length + 16
// just throw away the first block
I found a link in stackoverflow here use-3des-encryption-decryption-in-java,but in fact the method uses only two parameter:HG58YZ3CR9" and the "IvParameterSpec iv = new IvParameterSpec(new byte[8]);"
But the most strong option of triple des could use three different key to encrypt the message.So how to do that? I find a mehond in Cipher, which use "SecureRandom" as another parameter.So is this the right way?
The first method code is below:
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TripleDESTest {
public static void main(String[] args) throws Exception {
String text = "kyle boon";
byte[] codedtext = new TripleDESTest().encrypt(text);
String decodedtext = new TripleDESTest().decrypt(codedtext);
System.out.println(codedtext); // this is a byte array, you'll just see a reference to an array
System.out.println(decodedtext); // This correctly shows "kyle boon"
}
public byte[] encrypt(String message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final byte[] plainTextBytes = message.getBytes("utf-8");
final byte[] cipherText = cipher.doFinal(plainTextBytes);
// final String encodedCipherText = new sun.misc.BASE64Encoder()
// .encode(cipherText);
return cipherText;
}
public String decrypt(byte[] message) throws Exception {
final MessageDigest md = MessageDigest.getInstance("SHA-1");
final byte[] digestOfPassword = md.digest("HG58YZ3CR9"
.getBytes("utf-8"));
final byte[] keyBytes = Arrays.copyOf(digestOfPassword, 24);
for (int j = 0, k = 16; j < 8;) {
keyBytes[k++] = keyBytes[j++];
}
final SecretKey key = new SecretKeySpec(keyBytes, "DESede");
final IvParameterSpec iv = new IvParameterSpec(new byte[8]);
final Cipher decipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
decipher.init(Cipher.DECRYPT_MODE, key, iv);
// final byte[] encData = new
// sun.misc.BASE64Decoder().decodeBuffer(message);
final byte[] plainText = decipher.doFinal(message);
return new String(plainText, "UTF-8");
}
}
As per this document, simply pass the cipher a key that is 168 bits long.
Keysize must be equal to 112 or 168.
A keysize of 112 will generate a Triple DES key with 2 intermediate keys, and a keysize of 168 will generate a Triple DES key with 3 intermediate keys.
Your code seems to do something questionable to make up for the fact that the output of MD5 is only 128 bits long.
Copy-pasting cryptographic code off the internet will not produce secure applications. Using a static IV compromises several reasons why CBC mode is better than ECB. If you are using a static key, you should probably consider generating random bytes using a secure random number generator instead of deriving the key from a short ASCII string. Also, there is absolutely no reason to use Triple DES instead of AES in new applications.
In principle, the for-next loop to generate the DES ABA key does seem correct. Note that you can provide DESede with a 16 byte key from Java 7 onwards, which amounts to the same thing.
That said, the code you've shown leaves a lot to be desired:
I is not secure:
the key is not generated by a Password Based Key Derivation Function (PBKDF) using the (password?) string
the key is composed of two keys instead of three (using a triple DES or TDEA with an ABA key)
the IV is set to all zero's instead of being randomized
the "password" string is too short
Furthermore the following code mistakes can be seen:
using new sun.misc.BASE64Encoder() which is in the Sun proprietary packages (which can be removed or changed during any upgrade of the runtime)
throwing Exception for platform exceptions and runtime exceptions (not being able to decrypt is handled the same way as not being able to instantiate the Cipher)
requesting 24 bytes instead of 16 within the Arrays.copyOf() call (which seems to return 24 SHA-1 output while there are only 20 bytes)
To generate a 3DES 24 byte (168 bits used) DES ABC key from a password (like) String you should use PBKDF-2. Adding an authentication tag is also very important if man-in-the-middle attacks or padding oracle apply. It would be much secure and much more practical to upgrade to AES if you can control the algorithms being used as well.