I am working on a file encryption/decryption app. I am using a simple .txt file for testing. When I select the file from within the app and choose to encrypt, the entire file data is encrypted. However, when I decrypt only part of the file data gets decrypted. For some reason the first 16 bytes/characters doesn't get decrypted.
test_file.txt contents: "This sentence is used to check file encryption/decryption results."
encryption result: "¾mÁSTÐÿT:Y„"O¤]ÞPÕµß~ëqrÈb×ßq²¨†ldµJ,O|56\e^-’#þûÝû"
decryption result: "£ÿÒÜÑàh]VÄþ„- used to check file encryption/decryption results."
There aren't any errors in the logcat.
What am I doing wrong?
Method to encrypt file:
public void encryptFile(String password, String filePath) {
byte[] encryptedFileData = null;
byte[] fileData = null;
try {
fileData = readFile(filePath);//method provided below
// 64 bit salt for testing only
byte[] salt = "goodsalt".getBytes("UTF-8");
SecretKey key = generateKey(password.toCharArray(), salt);//method provided below
byte[] keyData = key.getEncoded();
SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
encryptedFileData = cipher.doFinal(fileData);
saveData(encryptedFileData, filePath);//method provided below
}
catch (Exception e) {
e.printStackTrace();
}
}
Method to read file content:
public byte[] readFile(String filePath) {
byte[] fileData;
File file = new File(filePath);
int size = (int) file.length();
fileData = new byte[size];
try {
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
inputStream.read(fileData);
inputStream.close();
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
catch (IOException e) {
e.printStackTrace();
}
return fileData;
}
Method to generate secret key:
private SecretKey generateKey(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
// Number of PBKDF2 hardening rounds to use. Larger values increase computation time. You
// should select a value that causes computation to take >100ms.
final int iterations = 1000;
// Generate a 256-bit key
final int outputKeyLength = 256;
SecretKeyFactory secretKeyFactory;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// Use compatibility key factory -- only uses lower 8-bits of passphrase chars
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1And8bit");
}
else {
// Traditional key factory. Will use lower 8-bits of passphrase chars on
// older Android versions (API level 18 and lower) and all available bits
// on KitKat and newer (API level 19 and higher).
secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
}
KeySpec keySpec = new PBEKeySpec(password, salt, iterations, outputKeyLength);
return secretKeyFactory.generateSecret(keySpec);
}
Method to save encrypted/decrypted data to the file:
private void saveData(byte[] newFileData, String filePath) {
File file = new File(filePath);
try {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write(newFileData);
outputStream.flush();
outputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
Method to decrypt file:
public void decryptFile(String password, String filePath) {
byte[] decryptedFileData = null;
byte[] fileData = null;
try {
fileData = readFile(filePath);
byte[] salt = "goodsalt".getBytes("UTF-8");//generateSalt();
SecretKey key = generateKey(password.toCharArray(), salt);
byte[] keyData = key.getEncoded();
SecretKeySpec sKeySpec = new SecretKeySpec(keyData, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
decryptedFileData = cipher.doFinal(fileData);
saveData(decryptedFileData, filePath);
}
catch (Exception e) {
e.printStackTrace();
}
}
This line of code encrypts the file:
//simple password for testing only
encryptor.encryptFile("password", "storage/emulated/0/Download/test_file.txt");
This line decrypts the file:
encryptor.decryptFile("password", "storage/emulated/0/Download/test_file.txt");
Edit: Thanks to DarkSquirrel42 and Oncaphillis. You guys are awesome!
Adding this line of code to both encrypt and decrypt functions solved my problem.
//note: the initialization vector (IV) must be 16 bytes in this case
//so, if a user password is being used to create it, measures must
//be taken to ensure proper IV length; random iv is best and should be
//stored, possibly alongside the encrypted data
IvParameterSpec ivSpec = new IvParameterSpec(password.getBytes("UTF-8"));
and then,
cipher.init(Cipher.XXXXXXX_MODE, sKeySpec, ivSpec);
your problem has something to do with the cipher's mode of operation ... cbc, or cipher block chaining mode
in general CBC is simple ... take whatever the output of your previous encryiption block was, and xor that onto the current input before encrypting it
for the first block we obviously have a problem... there is no previous block ... therefore we introduce something called IV ... an initialisation vector ... a block ength of random bytes ...
now ... as you can imagine, you will need the same IV when you want to decrypt ...
since you don't save that, the AES implementation will give you a random IV every time ...
therefore you don't have all information to decrypt block 1 ... which is the first 16 bytes in case of AES ...
when handling CBC mode data, it's allways a good choice to simply prepend the used IV in your cypertext output ... the IV shall just be random ... it is no secret ...
Like #ÐarkSquirrel42 already points out the en/decrytion routine for CBC seems to interpret the first 16 bytes as an initialisation vector. This worked for me:
// got to be random
byte[] iv = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(Cipher.XXXXX_MODE, sKeySpec,ivspec);
Related
From the php encryption function below:
$data = "1212312121447";
$cipher = "aes-256-ofb";
$secretKey = "aNdRgUjXn2r5u8x/A?D(G+KbPeShVmYp";
$ivLength = openssl_cipher_iv_length($cipher);
$keyOfb = substr(hash('sha256', $secretKey, true), 0, 32);
$ivOfb = substr($keyOfb, 0, $ivLength);
$encryptedOfb = openssl_encrypt($data, $cipher, $keyOfb, OPENSSL_RAW_DATA, $ivOfb);
echo "ofb-encrypted: " . base64_encode($ivOfb . $encryptedOfb);
the result of encryption is MyFTCJx8RPzOx7h8QNxEtQgeiNIRwnrJ+uc0V70=
And I have try to write this function in Java like this:
public static SecretKeySpec hashKey(String key){
String keyPass = key;
SecretKeySpec result = null;
try{
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(keyPass.getBytes());
byte[] AesKeyData = Arrays.copyOfRange(md.digest(), 0, 32);
SecretKeySpec keySpec = new SecretKeySpec(AesKeyData, "AES");
result = keySpec;
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return result;
}
public static String encryptedOFB(String inp){
String result = "";
String key = "aNdRgUjXn2r5u8x/A?D(G+KbPeShVmYp";
SecretKeySpec keyHashed = hashKey(key);
try{
byte[] initVectorSize = Arrays.copyOfRange(keyHashed.toString().getBytes(), 0, 16);
Cipher cipher = Cipher.getInstance("AES/OFB/NoPadding");
IvParameterSpec iv = new IvParameterSpec(initVectorSize, 0, cipher.getBlockSize());
cipher.init(Cipher.ENCRYPT_MODE, keyHashed, iv);
byte[] encrypted = cipher.doFinal(inp.getBytes());
ByteArrayOutputStream conc = new ByteArrayOutputStream();
conc.write(initVectorSize);
conc.write(encrypted);
byte[] concEnc = conc.toByteArray();
result = new String(Base64.getEncoder().encode(concEnc));
}
catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return result;
}
The result is amF2YXguY3J5cHRvLnNwZYUmrJNv8ycvLua0O9g=
Why my java function return the different result from php?
And how do I fix the java function to get the same result with php?
Thank you.
The IV is determined wrongly. Instead of keyHashed.toString().getBytes() you have to use keyHashed.getEncoded(). Then you get the result of the PHP code.
Apart from that, your key derivation is insecure: since the IV is the first 16 bytes of the key, the same password also means the same key/IV pair, which is insecure. For passwords, it is better to use a reliable key derivation function in conjunction with a randomly generated salt. The IV can be inferred along with the key or randomly generated independently. Salt (or IV) are not secret and can be passed with the ciphertext for decryption, usually concatenated.
When encoding (e.g. inp.getBytes()), the encoding should always be specified (e.g. StandardCharsets.UTF_8). Likewise with the decoding (new String(..., StandardCharsets.UTF_8)). Otherwise the default encoding is used, which can cause cross-platform problems.
This can be a duplicate of Exception: "Given final block not properly padded" in Linux, but it works in Windows but not entirely.
My application works in windows, but fails in Linux with
Given final block not properly padded exception
Configuration:
JDK Version: 1.8u162
Windows : version 10
Linux : OpenSuSE 42.2 (x86_64)
My code is below:
public static Cipher createCipher(int mode, String passPhrase) throws CypherException {
// Salt value
byte[] salt = new byte[128]; // Should be atleast 8 bytes
SecureRandom secRandom = new SecureRandom(passPhrase.getBytes(StandardCharsets.UTF_8));
secRandom.nextBytes(salt); // Self-seeded randomizer for salt
// Iteration count
int iterationCount = 12288;
int derivedKeyLength = 256 ; // Should be atleast longer than 112 bits. Depends on Key size of algorithm.
Cipher cipher;
try {
KeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, derivedKeyLength * 8);
SecretKey key = SecretKeyFactory.getInstance(APBEWithHmacSHA512AndAES_256).generateSecret(keySpec);
byte iv[] = new byte[16];
secRandom.nextBytes(iv); // Self-seeded randomizer to generate IV
IvParameterSpec ivSpec = new IvParameterSpec(iv) ; // IvParameterSpec initialized using its own randomizer
// Note: there is no typical transformation string. Algorithm, mode (CBC) and padding scheme (PKCS5Padding) is all taken care by ALGORITHM_NAME.
cipher = Cipher.getInstance(PBEWithHmacSHA512AndAES_256);
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount, ivSpec);
// Create the Cipher
cipher.init(mode, key, paramSpec);
} catch (NoSuchPaddingException nspe) {
throw new CypherException("No such Padding: " + nspe.getMessage());
} catch (NoSuchAlgorithmException nsae) {
throw new CypherException("Algorithm not supported: " + nsae.getMessage());
} catch (InvalidKeyException ike) {
throw new CypherException("Invalid key: " + ike.getMessage());
} catch (InvalidKeySpecException ikse) {
throw new CypherException("Invalid key specification: " + ikse.getMessage());
} catch (InvalidAlgorithmParameterException iape) {
throw new CypherException("Invalid algorithm parameter: " + iape.getMessage());
}
return cipher;
}
public static void decrypt(InputStream in, OutputStream out, String passPhrase) throws IOException, CypherException {
// Bytes read from in will be decrypted
in = new CipherInputStream(in, createCipher(Cipher.DECRYPT_MODE, passPhrase));
int numRead;
// Buffer used to transport the bytes from one stream to another
byte[] buf = new byte[1024];
// Read in the decrypted bytes and write the cleartext to out
try {
while ((numRead = in.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
} finally {
// close the streams
}
}
And I am getting
javax.crypto.BadPaddingException: Given final block not properly
padded
at following line:
while ((numRead = in.read(buf)) >= 0) {
Now, as suggested in Exception: "Given final block not properly padded" in Linux, but it works in Windows by Maarten Bodewes I am already using PBKDF2 as PBEWith*, really is the PBKDF2 + encryption scheme (CBC mode with PKCS5Padding). So why the encryption done on Windows doesn't work on Linux in my case? Can someone help please?
How would I go about this? I have code encrypting a file using AES/CBC/PKCS5Padding in Java and as I understand form this picture:
Partial decryption should be possible having part of ciphertext used by preceding block and the key. I have not found any examples of this, though. Any help?
The decrypting code is as fallows:
//skip the IV (ivSize is 16 here) - IV was pretended to the stream during encryption
data.skip(ivSize);
//skip n blocks
int n = 2;
System.out.println("skipped: " + data.skip(n*16));
byte[] iv = new byte[ivSize];
//use next 16 bytes as IV
data.read(iv);
// Hashing key.
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(encryptionKey.getBytes(StandardCharsets.UTF_8));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
CipherInputStream cis = new CipherInputStream(data, cipher);
try {
ByteStreams.copy(ByteStreams.limit(cis, limit), output);
} catch (IOException exception) {
// starting with java 8 the JVM wraps an IOException around a GeneralSecurityException
// it should be safe to swallow a GeneralSecurityException
if (!(exception.getCause() instanceof GeneralSecurityException)) {
throw exception;
}
log.warning(exception.getMessage());
} finally {
cis.close();
}
Yes it is possible.
You need to choose the "part" on a block boundary, a length that is a block size multiple and use the previous block as the IV. For AES the block size is 16-bytes.
If the "part" includes the last block specify the correct padding, otherwise specify no padding NoPadding: AES/CBC/NoPadding in this instance. This will eliminate a padding error. Only there last block is has/is padding.
Cipher must be instantiated with the correct padding option depending if it is the last block of the entire encrypted data or not.
See: PKCS#7 padding (sometimes called PKCS#5 in error) which is the most common padding.
I'm trying to encrypt something, and decrypt it. I'm failing on the decryption - I get the exception above. I tried changing ctLength and ptLength, but to no avail. What am I doing wrong?
I'm trying to encrypt: 0 0 0 0 0 0 0 0
private Cipher encrypt(byte[] input)
{
try
{
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
// encryption pass
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
FileOutputStream fs = new FileOutputStream(savedScoresFileName);
fs.write(cipherText);
return cipher;
}
catch (Exception e)
{
Log.e("encrtypt", "Exception", e);
}
return null;
}
private String decrypt()
{
try
{
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
byte[] cipherText = new byte[32];
FileInputStream fl = new FileInputStream(savedScoresFileName);
fl.read(cipherText);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = new byte[cipher.getOutputSize(32)];
int ptLength = cipher.update(cipherText, 0, 32, plainText, 0);
ptLength += cipher.doFinal(plainText, ptLength);
return new String(plainText).substring(0, ptLength);
}
catch (Exception e)
{
Log.e("decrypt", "Exception", e);
}
return null;
}
This code was copied from this, which worked.
Your code has a number of issues, but your problem is caused by your file reading code and your strange method of performing the encryption and decryption.
Don't use the update() method, just use doFinal() and correct your file writing/reading code. E.g. your decryption method should look something like:
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
// Here you need to accurately and correctly read your file into a byte
// array. Either Google for a decent solution (there are many out there)
// or use an existing implementation, such as Apache Commons commons-io.
// Your existing effort is buggy and doesn't close its resources.
byte[] cipherText = FileUtils.readFileToByteArray(new File(savedScoresFileName));
cipher.init(Cipher.DECRYPT_MODE, key);
// Just one call to doFinal
byte[] plainText = cipher.doFinal(cipherText);
// Note: don't do this. If you create a string from a byte array,
// PLEASE pass a charset otherwise your result is platform dependent.
return new String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
I have a password string in my android application. I need to the send the password through the .net web service (i.e. end with .aspx) using the SOAP web service. Before sending the password i need to encrypt the password with AES 128 encryption with the custom key and IV.
They have a encrypt/decrypt tool in .net with the custom key and Iv. The tool ask a custom key with 16 digit and IV 8 digit. If give the string it generate the encrypting string. example
Example:
Key : 1234567812345678
IV : 12345678
String : android
Encrypted string : oZu5E7GgZ83Z3yoK4y8Utg==
I didn't have any idea how to do this in android. Need help.
A complete example may help you:
The encrypt/decrypt functions, using IV
public static byte[] encrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static byte[] decrypt(byte[] data, byte[] key, byte[] ivs) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] finalIvs = new byte[16];
int len = ivs.length > 16 ? 16 : ivs.length;
System.arraycopy(ivs, 0, finalIvs, 0, len);
IvParameterSpec ivps = new IvParameterSpec(finalIvs);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
You can use it as below :
String dataToEncryptDecrypt = "android";
String encryptionDecryptionKey = "1234567812345678";
String ivs = "12345678";
byte[] encryptedData = encrypt(dataToEncryptDecrypt.getBytes(), encryptionDecryptionKey.getBytes(),
ivs.getBytes());
// here you will get the encrypted bytes. Now you can use Base64 encoding on these bytes, before sending to your web-service
byte[] decryptedData = decrypt(encryptedData, encryptionDecryptionKey.getBytes(), ivs.getBytes());
System.out.println(new String(decryptedData));
I don't know the details of AES algorithm in use(ie mode & padding method), bit it should be roughly like this:
public static byte[] encrypt(byte[] data, byte[] key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/ZeroBytePadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
byte[] empty = new byte[16]; // For better security you should use a random 16 byte key!!!
IvParameterSpec ivps = new IvParameterSpec(empty);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivps);
return cipher.doFinal(data);
} catch (Exception e) {
// ...
}
return null;
}
Function above could be used like this:
String data = "android";
String key = "1234567812345678";
byte encrypted = encrypt(data.getbytes("UTF-8"), key.getbytes("UTF-8"));