I am trying to encrypt a string in ruby and descrypt in Android. I'm pretty unfamiliar with ciphering, but I've done some reading and I think I'm close to getting to work. however, I'm still getting an error on the Android side that I just do not understand how to fix. I understand what padding is and that it's not correct, but what do I need to change to make this work? My ruby and java code are below. Thank you!!!
Ruby:
shared_key = "123456789012345678901234"
cipher = OpenSSL::Cipher::Cipher.new("des3")
cipher.encrypt
cipher.key = shared_key
ciphertext = cipher.update(secret)
ciphertext << cipher.final
Rails.logger.debug(ciphertext);
encrypted_secret = Base64.encode64(ciphertext)
Rails.logger.debug(encrypted_secret);
render json: { 'token' => token, 'secret' => encrypted_secret }, status: :ok
Java:
SecretKey key = new SecretKeySpec(SHARED_DECRYPTION_KEY.getBytes("UTF8"), "DESede");
byte[] encryptedSecretBytes = Base64.decode(secret);
Cipher cipher = Cipher.getInstance("DESede"); // cipher is not thread safe
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainTextSecretBytes = (cipher.doFinal(encryptedSecretBytes));
String decryptedSecret = Base64.encodeBytes(plainTextSecretBytes);
and the exception I get in Android:
05-14 19:03:11.500: W/System.err(22175): javax.crypto.BadPaddingException: pad block corrupted
05-14 19:03:11.500: W/System.err(22175): at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709)
05-14 19:03:11.500: W/System.err(22175): at javax.crypto.Cipher.doFinal(Cipher.java:1111)
05-14 19:03:11.500: W/System.err(22175): at com.cdlcollege.saas.Credentials.storeServerAccessCredentials(Credentials.java:85)
Pad block corrupted means the wrong key was used to decrypt or the data was altered between encryption and decryption.
If I had to guess, I suspect you are creating a key in the wrong manner. Instead of calling getBytes(), I'm guessing you should have done a hex conversion.
See Convert hex string to byte [] for example Android code for performing this task.
Side note: don't just specify "DESede" for a cipher. Specify the mode and padding as well. E.g. "DESede/CBC/PKCS5Padding". That ensures you get exactly what you want, rather than crypto provider defaults (which may vary across phones).
Related
I am trying to get the same amount of bytes of the below snippet in Java. I am working with decryption of encrypted strings with known key. However, I am unable to reproduce these two lines of code in Java.
let usrKey: NSData! = (usrKey as NSString).data(using: String.Encoding.utf8.rawValue) as NSData!
let keyBytes = UnsafeMutableRawPointer(mutating: usrData!.bytes)
How can I get the same keyBytes in Java?
What will the equivalent expression of the above in Java language?
What is the behavior of mutating ?
Java code I tried working with and the error below that.
byte [] keyBytes = userKey.getBytes("UTF-8");
SecretKeySpec secret = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, newIvParameterSpec(ivBytes));
byte[] outputBytes = cipher.doFinal(inputBytes);
input 16 bytes
iv 16 bytes
key 32 bytes
ALGORITHM - AES128/CBC/PKCS5Padding
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.doFinal(CipherCore.java:991)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
The issue is to find the right way to get the user key (could be short so padded) and create similar 32 bytes (required by the decryption algo) between Swift and Java code.
The Java line
byte [] keyBytes = userKey.getBytes("UTF-8");
should have the same effect as the two Swift lines. The problem is likely outside of the code that you showed in the question. You may want to debug both Swift and Java code to make sure the usrKey matches userKey and that the resulting keyBytes are the same in both Swift and Java.
Please see Apple documentation at https://developer.apple.com/documentation/swift/unsafemutablerawpointer/2427850-init for the meaning of mutating. Also consult Apple documentation for any other API-related questions.
I java 8 installed on client side where I am encrypting my data file using the below technique
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
outputStream = new CipherOutputStream(new FileOutputStream(encryptedFile), cipher);
And now i am decrypting on server side where i have Java 7 installed as per the code below.
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
inputStream = new CipherInputStream(new FileInputStream(encryptedFile), cipher);
outputStream = new FileOutputStream(decryptedFileName);
Doing so give me below error
Caused by: java.io.IOException: javax.crypto.BadPaddingException: Given final block not properly padded
at javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:115) [jce.jar:1.7.0_71]
at javax.crypto.CipherInputStream.read(CipherInputStream.java:233) [jce.jar:1.7.0_71]
at javax.crypto.CipherInputStream.read(CipherInputStream.java:209) [jce.jar:1.7.0_71]
Same code works fine when i have same java version (1.7) installed on both side.
How can we fix this so that without changing the java version either of the side
There are a number of possible causes for this issue:
You don't specify how you are getting/generating the key. If your JREs differ in their possession/absence of the JCE Unlimited Strength Jurisdiction Policies, one will support 256-bit AES encryption and the other will only support 128-bit. If you are generating a key based on the available key lengths, this could be causing the keys not to match. Meanwhile, both of your Java 7 environments may have the same level policies installed.
You are not specifying the block cipher mode of operation or padding scheme on either side of the system -- I recommend an AEAD mode like GCM, EAX, or CCM (CTR + CBC-MAC) in conjunction with NoPadding, but even CBC/PKCS5Padding or CTR/NoPadding are better than the default AES/ECB/PKCS5Padding that you will get just by invoking Cipher.getInstance("AES").
You don't explain how you are encoding the cipher text before persisting it and then deserializing it for decryption. Without a safe encoding scheme like hexadecimal or Base64, you may (read: eventually will) encounter encoding issues working with raw binary values.
Once you change from ECB to another mode of operation, you will need to provide the initialization vector (IV) for both encryption and decryption, and transmit the IV alongside the cipher text. The IV does not need to be encrypted in any way, but it must be unique and non-predictable for each message encrypted with the same key. As it is always the block size of the cipher (fixed at 16 bytes/128 bits for AES), simply prepend the cipher text with the IV value and then split it for decryption.
AES (and all symmetric cryptography) uses the same key for encryption and decryption -- there are no public and private keys involved. It could just be a naming issue, but the fact that you are trying to decrypt with publicKey may indicate the wrong key being used. You should verify that both the encryption and decryption keys are byte-identical (same length (16, 24, or 32 bytes) and equal). ECB "decryption" will always "succeed" if the cipher text is an exact multiple of the block size (16 bytes). Then the padding is verified. If you attempt to decrypt a message with the wrong key, you will often (255/256 times) get a padding error. The other case is that the last byte decrypts to 0x01, which is a valid padding value for PKCS #5/#7, so it won't detect a padding error.
Demonstration that AES/ECB/PKCS5Padding is the default on Java 8 (1.8.0_101):
#Test
public void testCipherGetInstanceShouldDefaultToECB() throws Exception {
// Arrange
final String PLAINTEXT = "This is a plaintext message."
final SecretKey key = new SecretKeySpec(Hex.decodeHex("0123456789ABCDEFFEDCBA9876543210" as char[]), "AES")
Cipher unspecified = Cipher.getInstance("AES")
final Cipher EXPECTED_CIPHER = Cipher.getInstance("AES/ECB/PKCS5Padding")
unspecified.init(Cipher.ENCRYPT_MODE, key)
EXPECTED_CIPHER.init(Cipher.DECRYPT_MODE, key)
// Act
byte[] cipherBytes = unspecified.doFinal(PLAINTEXT.getBytes(StandardCharsets.UTF_8))
logger.info("Cipher text: ${Hex.encodeHexString(cipherBytes)}")
// Assert
byte[] recoveredBytes = EXPECTED_CIPHER.doFinal(cipherBytes)
String recovered = new String(recoveredBytes, StandardCharsets.UTF_8)
assert recovered == PLAINTEXT
}
I tried to encrypt my plain data "hello" with key "01234567891234567890123456789012" but the problem is my encrypted code is not the same with the online reference.
Here is my android code written:
String smykey = "01234567891234567890123456789012";
String hellos = "hello";
SecretKeySpec key = new SecretKeySpec(smykey.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");//("AES/ECB/PKCS7Padding");//("ECB");//("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(hellos.getBytes());
code produce,
android: 0x25 0x66...0x2d 0x87 (32 bytes)
ref: 0xa3 0xef...0x68 0x9f (16 bytes)
What's wrong with this Android code? someone I appreciate your help please?
The online reference is doing something different, the Android code produces the correct encrypted data.
The data hellos is not a multiple of the block size (16-bytes) so you must specify padding (PKCS#7).
Android is using PKCS#7 padding and 25669d21 dfd0fd6f cfef6cce 4ef12d87 is the correct result for AES with a 256-bit key, ECB mode and PKCS#7 padding.
smykey and hellos were converted to data using UTF-8 encoding.
It is a rare case where ECB mode is used, it is not secure, use CBC mode with a random iv (prepend the iv to the encrypted data). Do not return padding errors on decryption.
Use an HMAC or better yet a key extending function such as PBKDF2 to securely except a string passcode to a secure encryption key. Using a string directly as a key is not secure.
I need to encrypt a string using SuiteScript, send it to a web service written in Java, and decrypt it there.
Using SuiteScript I'm able to encrypt and decrypt without any issue. But when I use the same key in java, I get different errors.
var x = "string to be encrypted";
var key = 'EB7CB21AA6FB33D3B1FF14BBE7DB4962';
var encrypted = nlapiEncrypt(x,'aes',key);
var decrypted = nlapiDecrypt(encrypted ,'aes',key);
^^works fine^^
The code in Java
final String strPassPhrase = "EB7CB21AA6FB33D3B1FF14BBE7DB4962"; //min 24 chars
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
SecretKey key = factory.generateSecret(new DESedeKeySpec(strPassPhrase.getBytes()));
Cipher cipher = Cipher.getInstance("DESede");
cipher.init(Cipher.DECRYPT_MODE, key);
String encrypted = "3764b8140ae470bda73f7ebed3c33b0895f70c3497c85f39043345128a4bc3b3";
String decrypted = new String(cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted)));
System.out.println("Text Decryted : " + decrypted);
With the above code, I get an exception javax.crypto.BadPaddingException: Given final block not properly padded
The key was generated using openssl
openssl enc -aes-128-ecb -k mypassphrase -P
it looks like you are encrypting with AES, and decrypting with DES. I think the ciphertext needs to be decrypted with the same symmetric algorithm that you used to encrypt.
Looks like currently you have to use Suitescript to decrypt messages if it was encrypted using SuiteScript.
See suiteanswers: 35099
The workaround suggested is to use an external javascript library to encrypt/decrypt. We ended up using OpenJS on the javascript, but on the java side had to make sure the defaults were adjusted according to what is setup on the javascript side. The Java APIs were more flexible in this regard than the javascript ones.
I am currently devloping a Windows application using C# and looking at developing a mobile app using Java.
The windows software and the mobile app will work together, the windows app will store information and encrypt certain information before storing it in an online database.
The mobile app will pull the information from the online database and will need to decrypt the encrypted string that is retrieved from the datbase.
The encryption method I am using in C# is below
byte[] clearTextBytes = Encoding.UTF8.GetBytes(encryptionString);
SymmetricAlgorithm rijn = SymmetricAlgorithm.Create();
MemoryStream ms = new MemoryStream();
byte[] rgbIV = Encoding.ASCII.GetBytes("ryojvlzmdalyglrj");
byte[] key = Encoding.ASCII.GetBytes("hcxilkqbbhczfeultgbskdmaunivmfuo");
CryptoStream cs = new CryptoStream(ms, rijn.CreateEncryptor(key, rgbIV), CryptoStreamMode.Write);
cs.Write(clearTextBytes, 0, clearTextBytes.Length);
cs.Close();
return Convert.ToBase64String(ms.ToArray());
The Windows method works fine.
The code I am using in Java is as follows:
KeySpec ks = new DESKeySpec("hcxilkqbbhczfeultgbskdmaunivmfuo".getBytes("UTF-8"));
SecretKey key = SecretKeyFactory.getInstance("DES").generateSecret(ks);
String ivString = "ryojvlzmdalyglrj";
byte[] ivByte = ivString.getBytes("UTF-8");
IvParameterSpec iv = new IvParameterSpec(ivByte);
//RC5ParameterSpec iv = new RC5ParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encoded = cipher.doFinal(Base64.decodeBase64("iNtaFme3B/e6DppNSp9QLg=="));
Log.d("Decoded Password", encoded.toString());
As both methods need to encrypt and decrypt the same string together it has to use the same key and IV. The only problem that I am having is in the java method the IVParameterSpec is going into the catch with an error that says IV must be 8 bytes long.
How can I resolve this to ensure that I have the same IV as C#.
Thanks for the help
The problem is that you are encrypting (in C#) with AES (also known as Rjindael), but trying to decrypt in Java with DES. If you change your Java code to use AES then it should all work fine.
DES uses an 8-byte IV because it works on 64-bit blocks. AES uses a 16-byte IV because it works on 128-bit blocks.
You should also make sure you use the same character encoding. In C# you are using ASCII, but in java you're using UTF-8. In your case they will be the same, but you should really fix it now to prevent strange bugs in future. You can change the character set name in Java to "US-ASCII" and it'll work.
You have to use the same algorithm of course. The default algorithm is for .NET is AES, so that is what you should be using on the Java side as well.