I am thinking about using ChaCha20-Poly1305 to encrypt/decrypt a password (I need to encrypt/decrypt cannot only hash). But, to use this algorithm we are required to use a Secret Key. However, this is a problem because I am encrypting/decrypting the user's password on their device, so in my DB I just store their encrypted password.
The problem with it, is if the user uninstall my app from their phone or change to a new phone, I would need the same secret key to be able to decrypt the user's password again.
My question is: How do I safely transfer and store this secret key ?
Also, please let me know if you have better alternatives for my problem, such as different encryption algorithm or methods.
Here is a code example with the SecretKey object that I've mentioned:
package com.javainterviewpoint;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class ChaCha20Poly1305Example
{
static String plainText = "This is a plain text which will be encrypted by ChaCha20 Poly1305 Algorithm";
public static void main(String[] args) throws Exception
{
KeyGenerator keyGenerator = KeyGenerator.getInstance("ChaCha20");
keyGenerator.init(256);
// Generate Key
SecretKey key = keyGenerator.generateKey();
System.out.println("Original Text : " + plainText);
byte[] cipherText = encrypt(plainText.getBytes(), key);
System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(cipherText));
String decryptedText = decrypt(cipherText, key);
System.out.println("DeCrypted Text : " + decryptedText);
}
public static byte[] encrypt(byte[] plaintext, SecretKey key) throws Exception
{
byte[] nonceBytes = new byte[12];
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// Create IvParamterSpec
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
// Initialize Cipher for ENCRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
// Perform Encryption
byte[] cipherText = cipher.doFinal(plaintext);
return cipherText;
}
public static String decrypt(byte[] cipherText, SecretKey key) throws Exception
{
byte[] nonceBytes = new byte[12];
// Get Cipher Instance
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
// Create IvParamterSpec
AlgorithmParameterSpec ivParameterSpec = new IvParameterSpec(nonceBytes);
// Create SecretKeySpec
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "ChaCha20");
// Initialize Cipher for DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
// Perform Decryption
byte[] decryptedText = cipher.doFinal(cipherText);
return new String(decryptedText);
}
}
For symmetric encryption and decryption, a best practice is use a MASTER KEY which is fixed string or has a fixed algorithm which just you know how to generate it.
You must use this MASTER KEY for encryption and decryption all other keys (name WORKING KEYS) to encrypt/decrypt real data.
If your device has a secure area which you can save your keys safely, you must inject master key on it by another device or by communicating with a server. If it has not, you are in RISK for disclosing keys.
However, if you are the only programmer of the app, you can use a fixed algorithm to generate MASTER key. For example hash(xor( of 1c1c1c with the ID of the device. Now, the device can send a request for getting working keys (by sending its id and mac-block which is encrypted by master key) to a server and you give back the right working keys.
This scenario use in payments by ISO-8583.
Related
Hi I am facing a decryption problem. The decrypted value is not matching the original one.
Here is my logic for encryption :
public byte[] encrypt(String plainText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
}
And this is my logic for Decryption
public String decrypt(byte[] cipherText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8);
}
Encryption options :
ENCRYPTION_ALGORITHM = "DESede";
ENCRYPTION_INSTANCE = "DESede/CBC/PKCS5Padding";
Integer ENCRYPTION_PARAM_SIZE = 8;
This is how I am trying to verify :
public static void main(String[] args){
Long value = 9123456L;
String strval = value.toString();
byte[] encryptedVal = encrypt(strval);
String decryptedVal = decrypt(encryptedVal);
System.out.println("Original value : " +strval);
System.out.println("Encrypted value : " +encryptedVal.toString());
System.out.println("Decrypted value : " +decryptedVal);
System.out.println("Final value : " +Long.parseLong(decryptedVal));
}
What I need to do here to make it work.
Note : The above code is working fine if I use the below logic without SecureRandom :
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(new byte[ENCRYPTION_PARAM_SIZE]));
Like others have said, your issue is that the IV you are using for encryption is different than the one you are using for decryption. The IV is not sensitive (from a confidentiality point of view) so it can be transmitted alongside the ciphertext. Some applications prepend the IV to the ciphertext while others use more standard formats such as CMS Encrypted Data. Since you are using Java, you can generate CMS Encrypted Data structures using Bouncy Castle's CMSEncryptedDataGenerator class.
There are two other issues with your code:
Choice of cryptographic primitives
Key management
Cryptographic Primitives
The cryptographic transformation you are using is "DESede/CBC/PKCS5Padding". Unless you have a good reason to use DES/3DES, you should consider switching to AES. Additionally, I would recommend using an AEAD such as AES-GCM or AES-GCM-SIV.
Key Management
All the cryptography in the world doesn't mean much if the keys aren't managed correctly. From your code it looks like you are constructing the SecretKeySpec object out of the key's bytes. If you can, try to use an hardware security module (HSM) or a key management system (KMS) where the key never gets exported from the tamper bounds of the device in plaintext format. AWS KMS, Azure Key Vault, and Google KMS all offer very affordable pricing for this. Of course, there are other options as well.
In the decrypt function you generate a random Initialization Vector (IV), so this won't ever work.
You need to store the IV from the encrypt function and provide it as an input to the decrypt function.
Here's an example:
public byte[] encryptAndDecrypt(String plainText) {
byte iv[] = new byte[ENCRYPTION_PARAM_SIZE];
SecureRandom secRandom = new SecureRandom();
secRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] cipherText=cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return decrypt(cipherText, iv)
}
public String decrypt(byte[] cipherText, byte[] iv) {
Cipher cipher = Cipher.getInstance(ENCRYPTION_INSTANCE);
SecretKeySpec key = new SecretKeySpec(fixSecret(encryptionKey), ENCRYPTION_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(cipherText);
}
Note that by definition the IV should be random but shouldn't be treated as a secret so you can store it as plain data without any protection.
The idea behind the IV is to randomize the cipher text so if you're not using IV, or using a constant IV, and encrypt "X", cipher text is "Y", you could easily reverse the cipher text into plain text, while with random IV the cipher text is different every time.
Currently i've created a SecretKey for use in a RC4 encryption for my assignment. After the RC4 encryption i would then convert this key into string and send it to a server via UDP however when i rebuild it on the server side using SecretKeySpec, it would produce a completely different secret key.
I've looked around stackoverflow for solutions but the end would still result in a rebuilt SecretKey being different from my original SecretKey.
I've tried rebuilding the Secret Key from the String format on the client code and the result would still be a different SecretKey compared to the original so i doubt my UDP tranmission has anything to do with the result.
Below are how i went about creating the initial SecretKey for use in a RC4 encryption:
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
How i converted the SecretKey to String and rebuilt using SecretKeySpec:
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
When I println "originalSecretKey" and "rebuiltSK" for checking, this is where I realised the rebuilt values are completely different and therefore i wouldnt be able to decrypt any originalSecretKey-encrypted message using the rebuiltSK.
Edit1: Silly me, thank you to "A Developer" and "Daniel" for pointing out that the actual .getEncoded() values of "originalSecretKey" and "rebuiltSK" are the same.
Apologies if I'm missing something extremely basic regarding Key generation and java's cryptography as this is my first time using them. Thank you in advance for your help !
Edit2:
Below is the code i'm currently using for my RC4 encryption and decryption:
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
//Cipher.DECRYPT_MODE when on server program
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
The code above is the reason why I'm trying to rebuild the secretKey on the server end after receiving it in byte[] from the client.
I've tried running the decryption with the "rebuiltSK" as the SecretKey argument however it doesn't produce the correct plaintext although I've checked the packet.getData() on both client and server to be the same.
Your rebuilding of the SecretKey works like expected and the encryption followed by the decryption retrieves the
original plaintext.
I can only that argue (same as #Daniel) that the key was changed during transmission or the (byte[] with the)
ciphertext was not fully transmitted to the server.
The below full example code shows a complete round with key generation, encryption and decryption.
This is the result:
plaintext equals decryptedtext: true
decryptedtext: my secret
Security warning: The code below uses an UNSECURE algorithm 'RC4' or 'ARCFOUR'.
Please do not copy below code or use it in production - it is for educasional purposes only.
The code does not have any proper exception handling !
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("https://stackoverflow.com/questions/63185927/java-secretkey-to-string-and-rebuilding-back-to-secretkey-produces-different-de");
// security warning: the algorithm 'RC4' or 'ARCFOUR' is unsecure and
// should be used for educational purposes only
// do not use this code in production
// key generation
KeyGenerator keygen = KeyGenerator.getInstance("RC4");
SecretKey originalSecretKey = keygen.generateKey();
// encryption
byte[] plaintext = "my secret".getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = encryptRC4(plaintext, originalSecretKey);
// decryption
String k = Base64.getEncoder().encodeToString(originalSecretKey.getEncoded());
byte[] decodedKey = Base64.getDecoder().decode(k);
SecretKey rebuiltSK = new SecretKeySpec(decodedKey, "RC4");
byte[] decryptedtext = decryptRC4(ciphertext, rebuiltSK);
// output
System.out.println("plaintext equals decryptedtext: " + Arrays.equals(plaintext, decryptedtext));
System.out.println("decryptedtext: " + new String(decryptedtext));
}
public static byte[] encryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.ENCRYPT_MODE, k);
byte[] encrypted = cipher.doFinal(b);
return encrypted;
}
public static byte[] decryptRC4(byte[] b, SecretKey k) throws Exception
{
Cipher cipher = Cipher.getInstance("RC4");
cipher.init(Cipher.DECRYPT_MODE, k);
byte[] decrypted = cipher.doFinal(b);
return decrypted;
}
}
I'm making a program which works with messages cryptography by Socket. But, when in my messages has a "o", or "b", or "c" and another letters, i receives that Exception in the decrypto moment.
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975)
at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at teste1.Decrypt.decrypt(Decrypt.java:15)
at teste1.Server.main(Server.java:24)
Yep, my message arrives completed with all the characters, so i don't think in some character was lost in the trasmission. So i don't really know what's the problem, because i've tried to changes a lot of things, but i continued recieving this Exception.
Decrypt class:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Decrypt{
String IV = "AAAAAAAAAAAAAAAA";
public String decrypt(String str, String keys) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
decrypt.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return new String(decrypt.doFinal(str.getBytes()),"UTF-8");
}
}
If wants the encrypt class too:
package teste1;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encrypt {
String IV = "AAAAAAAAAAAAAAAA";
public byte[] encrypt(String menE, String keys) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/EBC/PKCS5Padding", "SunJCE");
SecretKeySpec key = new SecretKeySpec(keys.getBytes("UTF-8"), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(IV.getBytes("UTF-8")));
return encrypt.doFinal(menE.getBytes());
}
}
That happens because Strings change your bytes, you should really use Base64
if strings are a must.
If you want to test that run this code:
byte[] aByte = {-45};
System.out.println(Arrays.toString(new String(aByte, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8)));
It will output: [-17, -65, -67] (which is not -45).
Anyways so a few tips for you:
You cannot encrypt with "ECB" and decrypt with "CBC".
An IV should not be a constant. you should generate a new IV for every message and send it along with the message.
Don't specify "UTF-8" use StandardCharsets.UTF_8 (note if using android: StandardCharsets.UTF-8 is API 19+ so you should have a constant for Charset.forName("UTF-8"))
Here is some example code for how to do it with Base64:
public byte[] encrypt(String message, String key, String iv) throws Exception {
Cipher encrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
encrypt.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return encrypt.doFinal(/*Get bytes from your message*/message.getBytes(StandardCharsets.UTF_8));
}
public String decrypt(String encryptedMessage, String key, String iv) throws Exception{
Cipher decrypt = Cipher.getInstance("AES/CBC/PKCS5Padding", "SunJCE");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
decrypt.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(Base64.getDecoder().decode(iv)));
return new String(decrypt.doFinal(Base64.getDecoder().decode(encryptedMessage)), StandardCharsets.UTF_8);
}
And run it with
//your message
String message = "Hello World!";
//generate a new AES key. (an AES key is just a random sequence 16 bytes)
SecureRandom random = new SecureRandom();
byte[] aesKey = new byte[16];
random.nextBytes(aesKey);
//generate a new initialization vector (iv) which is also a random sequence of 16 bytes.
byte[] iv = new byte[16];
random.nextBytes(iv);
String aesKeyAsString = Base64.getEncoder().encodeToString(aesKey);
String ivAsString = Base64.getEncoder().encodeToString(iv);
//encrypt
byte[] encrypted = encrypt(message, aesKeyAsString, ivAsString);
//enocde your encrypted byte[] to String
String encryptedString = Base64.getEncoder().encodeToString(encrypted);
//decrypt
String decrypted = decrypt(encryptedString, aesKeyAsString, ivAsString);
//print your results
System.out.println("Encrypted: " + encryptedString + " Decrypted: " + decrypted);
Outputs:
Encrypted: |encrypted string depended on the generated key and iv| Decrypted: Hello World!
You can also use the more efficient way and use byte[] instead of Strings but it's your choice.
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
So I'm writing a program to encrypt and decrypt text files but I seem to be always getting this error when I use an encrypthion other than "Blowfish" (e.g. "Blowfish/CBC/PKCS5Padding"). The excepthiong I get is:
Exception in thread "main" java.security.NoSuchAlgorithmException: Blowfish/CBC/PKCS5Padding KeyGenerator not available
at javax.crypto.KeyGenerator.<init>(DashoA13*..)
at javax.crypto.KeyGenerator.getInstance(DashoA13*..)
at Encryptor.<init>(Encryptor.java:87)
at Encryptor.main(Encryptor.java:30)
A portion of my code:
import java.security.MessageDigest;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor2 {
private IvParameterSpec ivSpec;
private SecretKeySpec keySpec;
private Cipher cipher;
public static void main(String[] args) throws Exception {
Encryptor2 e = new Encryptor2(
"averylongtext!#$##$##$#*&(*&}{23432432432dsfsdf");
String enc = e.encrypt("john doe");
String dec = e.decrypt(enc);
}
public Encryptor2(String pass) throws Exception {
// setup AES cipher in CBC mode with PKCS #5 padding
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// setup an IV (initialization vector) that should be
// randomly generated for each input that's encrypted
byte[] iv = new byte[cipher.getBlockSize()];
new SecureRandom().nextBytes(iv);
ivSpec = new IvParameterSpec(iv);
// hash keyString with SHA-256 and crop the output to 128-bit for key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(pass.getBytes());
byte[] key = new byte[16];
System.arraycopy(digest.digest(), 0, key, 0, key.length);
keySpec = new SecretKeySpec(key, "AES");
}
public String encrypt(String original) throws Exception {
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] encrypted = cipher.doFinal(original.getBytes("UTF-8"));
System.out.println("encrypted: `" + new String(encrypted) + "`");
return new String(encrypted);
}
public String decrypt(String encrypted) throws Exception {
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decrypted = cipher.doFinal(encrypted.getBytes("UTF-8"));
System.out.println("decrypted: `" + new String(decrypted, "UTF-8")
+ "`");
return new String(decrypted, "UTF-8");
}
}
But now it fails with Input length must be multiple of 16 when decrypting with padded cipher
The additional parameters that you're specifying with the algorithm are meant for Cipher. For KeyGenerator and SecretKeySpec you only specify the algorithm. The other parameters are for the cipher mode of operation and padding to be used. For example, if you're using Blowfish in CBC mode with PKCS #5 padding you want:
KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
Cipher cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding");
See Encrypting and Decrypting Using Java: Unable to get same output for an example. It uses the same mode and padding as you have. The only difference is that uses AES instead of Blowfish, however it works exactly the same.