I am new to blackberry development and got to complete a task of encryption and decryption with AES/ECB/NoPadding. I used below code, from internet.
Encryption method:
public static byte[] encrypt( byte[] keyData,String message )
throws Exception
{
byte[] data = message.getBytes("UTF-8");
// Create the AES key to use for encrypting the data.
// This will create an AES key using as much of the keyData
// as possible.
if ((data.length % 16) != 0 ) {
StringBuffer buffer = new StringBuffer(message);
int moduleOut = data.length % 16;
int padding = 16 - moduleOut;
for(int i = 0 ; i < padding; i++){
buffer.append(" ");
}
data = buffer.toString().getBytes("UTF-8");
}
AESKey key = new AESKey( keyData);
NoCopyByteArrayOutputStream out = new NoCopyByteArrayOutputStream(data.length);
AESEncryptorEngine engine = new AESEncryptorEngine(key);
BlockEncryptor encryptor = new BlockEncryptor(engine, out);
encryptor.write(data,0,data.length);
int finalLength = out.size();
byte[] cbytes = new byte[finalLength];
System.arraycopy(out.toByteArray(), 0, cbytes, 0, finalLength);
// encryptor.close();
// out.close();
return cbytes;
}
Decryption method:
public static byte[] decrypt(byte[] keyData, byte[] base64EncodedData)
throws CryptoException, IOException
{
// String base64EncodedData=new String(base64EncodedData);
byte[] cipherText =Base64ToBytes(new String(base64EncodedData));
// First, create the AESKey again.
AESKey key = new AESKey(keyData);
// Now, create the decryptor engine.
AESDecryptorEngine engine = new AESDecryptorEngine(key);
// Create the BlockDecryptor to hide the decryption details away.
ByteArrayInputStream input = new ByteArrayInputStream(cipherText);
BlockDecryptor decryptor = new BlockDecryptor(engine, input);
// Now, read in the data.
byte[] temp = new byte[100];
DataBuffer buffer = new DataBuffer();
for (;;)
{
int bytesRead = decryptor.read(temp);
buffer.write(temp, 0, bytesRead);
if (bytesRead < 100)
{
// We ran out of data.
break;
}
}
byte[] plaintext = buffer.getArray();
return plaintext;
}
Base64 to Bytes convert method:
private static byte[] Base64ToBytes(String code) {
byte[] aesString = null;
try
{
aesString = Base64InputStream.decode(code);
}
catch (IOException ioe)
{
}
return aesString;
}
Now the problem is when i encrypt my string with above method i get padded unicodes at the end of the string, which is not tolerable at Server side.
I know it is due to PKCS5FormatterEngine and its normal to have characters appended at end of the string while using this class.
But what if i want to encrypt and decrypt string with AES/ECB method and that too with NoPadding. I know ECB is not a secure mode and all that but server is with PHP and ready, works great in Android, and J2ME.
Please guide. How to bypass PKCS5FormatterEngine or encrypt without any padding.
Update:
I tried to use Cipher class in Blackberry as i used in Android and J2ME but it seems unavailable in net_rim_api.jar, even i tried downloading bouncy castle jar file and dependent class NoSuchAlogrithmException so java.security jar (org.osgi.foundation-1.0.0.jar), compiles but when i try to run it stops saying duplicate classes found. It has a problem with few duplicate classes in jar i have kept for java.security.
If you have solution towards this, please let me know.
Update for Answer:
I have update my code with full encryption and decryption code and also check the answer for better understanding.
Not sure this is really an answer in general, but it might help in this specific case, so I am adding it as such.
You can't really use AES without some padding, because the AES processing does not wish to assume the data you supply will be a multiple of 16 bytes. However if you actually always supply a buffer that is a multiple of 16 bytes, then you can just encrypt your data with code like this:
AESEncryptorEngine engine = new AESEncryptorEngine( key );
for ( int j = 0; j < ciphertext.length - 15; ) {
engine.encrypt(plainText, j, ciphertext, j);
j = j+16;
}
So how do you make sure this works OK at the other end? It may or may not be possible to do this - it actually depends on what is being transferred.
But if, for example, you were passing XML data, then you can append spaces to make the data up to a 16 byte boundary and these will be decrypted as spaces by the Server, and then ignored by the parsing. You can add redundant padding bytes to a variety of file formats and the padding will be ignored, it all depends on the file format being processed.
Update
Given that the actual data is JSON and that I believe that JSON will ignore trailing spaces, the approach I would take is append spaces to the JSON data before encrypting it.
So convert the JSON string to bytes:
byte [] jsonBytes = jsonString.getBytes("UTF-8");
pad this if you need too:
if ( (jsonBytes.length % 16) != 0 ) {
// Now pad this with spaces
}
and you can encrypt the result with no worries about padding bytes.
Related
I'm trying to encrypt any java object (in this example an Integer, but Date should also work) to a base64 string using the Cipher class.
Basically I convert the given object into a byte array using ByteArrayOutputStream and encrypt this byte array with Cipher. See below
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"));
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
The output
0 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94LOaOdEXeZZm8qNoELOLdj
1 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97aK6ELffW8n7vEkNAbC9RW
2 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97mJ1m8lVtjwfGbHbMO2rxu
3 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa942rroZJbe2KN0/t8ukOkWd
4 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97rbkvF4HLzuvGTm4JMJw+2
5 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94zvSlIQe8RQI8t5/H74ShO
6 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97tNLWZHmR0rNkDXZtVWA2Y
7 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa94lr84KZ6MnUsPOFyJIfDTB
8 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97e6ihJ8SXmz9sy9XXwWeAz
9 - BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa97neBL2tLG2TXgCI/wDuyMo
seems strange to me because of the same prefix BroJyDQdUDVYwq6tUdk9UcgIX8R7+B474UFw/HFx9lGpDjC0ilKxw8fYd1hFB54f8shrn/XIT52WzcOsH0CBGJ3bva8Rk1h4uYo5sfpJa9
for every encrypted object. In this example, I'm using the same key for every object but this should not be the reason for this issue.
I've also tested this example with Strings and Dates instead of Integers. Encoding Dates into byte arrays and encrypting them with the same method also lead
into this issue having an identical prefix for all Date objects, while encoding Strings with the same method seem to work fine. Every encoded and encrypted
String leads to another encrypted base64 String. See below
Outputs for encrypted Dates: (also with identical prefix)
0 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRj1HrbSaioOqhbM2uZi2r0
1 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQ0q0kophfAfiPxe0U+sb1R
2 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JTeTKnbYsLo6TjfuQF9PYIk
3 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSrDPGtepg4HWUL6VeBtWg7
4 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JS7dlSsNjnY011F2BooNnKW
5 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JStO2xPQvT76/k+xMdaDBpQ
6 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JQqz4J3yO8G9taHi7b/Zefl
7 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JR8/fOAiuGM8tO8zMcju4Xk
8 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JSMDHi6UyD5QQY1jRXNCErc
9 - cpQxMKQW7mHCKsxxsyMMJTRPnfgujbJYLiVKeHgM2JRfKstfsC8dPYuPfd9f2B+B
Outputs for encrypted Strings: (works as expected)
0 - TNpI3oLRzH5id6c/yRJlQQ==
1 - yMkm+ZuYWs4EnISo56Zljw==
2 - 03i1Lv01Nn2sGDGmtpRAIg==
3 - 5skvWbkcVXfT2TScaGxNfQ==
4 - 0p9qg5U+DqAnCBdyji+L9Q==
5 - gD5xPtAMy34xC90hKCQeWA==
6 - oQwKUhuxC5X/f6U9G9la8Q==
7 - 72cvCiLks3DDaTLAQvoVfw==
8 - wQu7Ug5RHg5egbNTI0YXQw==
9 - x1BQVwy3r6MP3SDLl/mktw==
Any idea?
Edit:
Even when I use CBC or another encryption method like DES or Blowfish the same issue occurs. I expect that every byte array from ByteArrayOutputStream should be encrypted into a completely different base64 string even if they have an identical prefix with ~90% of their length.
Using object serialization before encryption is not a great idea. Either you encrypt the data for transport protection, in which case TLS makes much more sense. Or you are encrypting for longer time storage, in which case serialization is dangerous as the serialization format could change. Heck, you might want to change the entire language / runtime in the future.
I'd suggest you generate your own protocol. In that case you can for instance simply encode an integer to 4 bytes using ByteBuffer#putInt(int) or by using DataOutputStream#writeInt(int). That way your integer just takes the minimum amount of 4 bytes (as an unsigned 32 bit big endian value). For very complex methods you may even look at ASN.1 structures and encodings (which are implemented in Bouncy Castle, among other libraries).
A Java Date is really just a long internally, which can be perfectly stored in 8 bytes. Another option is to encode it to an (UTC) date string and store that using US ASCII compatible encoding (StandardCharsets.US_ASCII).
Beware that ECB mode is very dangerous. For instance, imagine that values above 0x00FFFFFF are uncommon and that you don't want to leak the presence of such values. Also imagine that the most significant byte is the last byte of a block that is filled with header bytes otherwise. In that case it is very easy to distinguish blocks with e.g. 0x01 from blocks with 0x00 which should be more common in this situation. So you immediately leak information about your plaintext.
This problem is just as prominent in CBC mode if you use a static IV rather than a random (or at least fully unpredictable) IV value. You have to use a random IV for each CBC encryption to be secure. You can store the IV together with the ciphertext. Commonly for CBC the 16 byte IV is simply prefixed to the ciphertext. However, preferably you should be using the authenticated GCM mode with a 12 byte random nonce instead.
It's a bit of a shame that Java allows reuse of the Cipher instances at all - as it for instance doesn't allow the Cipher to destroy key material after usage. That it defaults to an insecure mode where the IV is repeated is doubly shameful. You'll have to take care of the IV issue yourself.
Example of using GCM and ByteBuffer:
public static void main(String[] args) throws Exception {
// input, a date and message
Date date = new Date();
String message = "hello world";
// AES-128 key (replace by a real 256 bit key in your case)
SecretKey aesKey = new SecretKeySpec(new byte[128 / Byte.SIZE], "AES");
// default nonce sizes for GCM, using a constant should be preferred
int nonceSize = 96;
int tagSize = 128;
String cts;
try (StringWriter stringWriter = new StringWriter(); PrintWriter out = new PrintWriter(stringWriter)) {
for (Integer i = 0; i < 10; i++) {
byte[] randomNonce = createRandomIV(nonceSize);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, randomNonce);
byte[] encodedMessage = message.getBytes(StandardCharsets.UTF_8);
ByteBuffer encodedNumberDateAndMessage = ByteBuffer.allocate(Integer.BYTES + Long.BYTES + encodedMessage.length);
encodedNumberDateAndMessage.putInt(i);
encodedNumberDateAndMessage.putLong(date.getTime());
encodedNumberDateAndMessage.put(encodedMessage);
// for reading we need to flip the buffer
encodedNumberDateAndMessage.flip();
ByteBuffer encryptedNumberDateAndMessage =
ByteBuffer.allocate(nonceSize / Byte.SIZE + encodedNumberDateAndMessage.limit() + tagSize / Byte.SIZE);
encryptedNumberDateAndMessage.put(randomNonce);
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
gcm.doFinal(encodedNumberDateAndMessage, encryptedNumberDateAndMessage);
// not required, we'll be using array() method
// encryptedNumberDateAndMessage.flip();
// we can use the full array as there
String base64Ciphertext = Base64.getEncoder().encodeToString(encryptedNumberDateAndMessage.array());
if (i != 0) {
out.write('\n');
}
out.write(base64Ciphertext);
}
cts = stringWriter.toString();
}
System.out.println(cts);
// TODO decrypt ciphertexts in cts
// hint use BufferedReader to read lines and don't forget to strip off the IV/Nonce first
}
private static byte[] createRandomIV(int sizeInBits) {
if (sizeInBits % Byte.SIZE != 0) {
throw new IllegalArgumentException("Invalid IV size, must be a multiple of 8 bits");
}
byte[] randomNonce = new byte[sizeInBits / Byte.SIZE];
SecureRandom rbg = new SecureRandom();
rbg.nextBytes(randomNonce);
return randomNonce;
}
Which produces the seemingly random output:
LHMsZPgZOz7nEcN5adB03+twTG2/ITfPnUUy4DxdgFEBAxm3HNDg8eXVnuvo80i4WMjY
eRJuw1ynrD3GeMmFTYiQc6VxelJuz8wHZtbl+7cepteKdtzcsdIDcDHBqvfjyzZp6WXd
MOkTLt4pk+sFm6I+CH4c90lxrRmwFKmS1wbX5eRSZYy6xqEjSz6iGC1vBXkPbl3k1C5r
cB5hKbpiAeNmbZYy1vdK5vissWYlkL6h6XJEYEFZaK7M097LkVAB01nu5GtCBUjPMjrK
LHzr/iudU3BPYmrimAIugjSckzXrzm03Ucgyb8laKktbh/Um4K2nyAGE2+T1aLH6JaYX
dg9SmcPl+dolHSIQPyvMUEPyu3VLSNPbN7ErPY93sjfKVyZsaGgft/cP4kUzNWEyRgAo
PiLHu4TKZMfBlFXst1867hEywST3RBbSSQ1g9D4DOkqh3oPkvsXP5INIEANZr2BHta38
4pJITAvij26NphYf9/ry5yGm+qPAaNG0Hqrk5ruVa60+V7k0jqDozjsST8OygyvkLrgY
HI6I3UHgzBNjskSJeo9fS3Cw3oKY8tneFbChtLz35DbcASOjpi7U9LKTL39lBTOBaZkG
jRycn4uSfT6JlDk3jn64wTL07I7bHvTSPSbWVG7XdKeSgOibW7FiCtTXojDPi8iywD58
which consists of the nonce, the ciphertext of an integer, a long value representing the date and the "hello world" string and finally the authentication tag, which is considered part of the ciphertext in Java.
The behavior you see is the result of you using the no-mode ECB mode in combination with similarity in the plaintext. You will have the same issue with all block ciphers (AES, Blowfish, DES).
When using CBC all that goes away if you provide IVs as needed:
public class Main {
static Random rand = new SecureRandom();
public static IvParameterSpec generateIv() {
byte[] ivBytes = new byte[16];
rand.nextBytes(ivBytes);
return new IvParameterSpec(ivBytes);
}
public static void main(String[] args) throws Exception {
for (Integer i = 0; i < 10; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput oos = new ObjectOutputStream(bos);
oos.writeObject(i);
oos.flush();
byte[] data = bos.toByteArray();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = generateIv();
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec("&E(H+MbQeThWmZq4".getBytes("UTF-8"), "AES"), iv);
String base64output = Base64.getEncoder().encodeToString(cipher.doFinal(data));
System.out.println(i + " - " + base64output);
}
}
}
Output:
0 - jt3Mk13pGjeaFf1oNq4LfmQ4z/31nRG4KtZ4H3RK6k/GA1anC3/lrzSXoLsQ6jMsVEpnxU13wAu6lkZJ3it1Ei4i4EsNFixc+YX4K6cIIv4ByY5Q246jd3H0m11C2FZJ
1 - Jqd0RB6lOITqifAaWluW6jx8F8gY4btZHx12CiXtZjfnehhtk64jva4eGTQd4EpvB/5Q/ORhZCNgF3ue0/Na1R9MCsK+mULAcyANdNcLyKbXo272G21z0LPCeweXdjhu
2 - xHdCG6rWNDyLTl8zruo8u+45V/RMXkrB7K+QU5r9lpc3FzvDwpl0wmy9Yj3FOyjMulmVT1zahH+wWVrmB9gNcXy7sGyCH/anJANC396OcDyQXqNIyvOPw9mpUmmRQcwR
3 - ygIDkLtQTupkbB35SzRflE3RAMmdYGSkdGZgRctFHdZCqGt+Arb3RbvhoAiiE9PwkyLmifyllQTTSutvV/ZtlGaGMX3v4bQUZDoaSyXQd9xn+pUSJk87NDVGi37xWw1O
4 - cJYSthCHHGeCqnuBJY8YdUbptKD3XNb2nt+pyIc94vRvjquYf7atu0+bDndFnePWvrlPzFIFXVB8CuANIsDhzRSNEOOU/wOkwcAN2AdavCqlZqN0Mtqdg4vqKGWx2oAE
5 - f7/gu8fJ8jkyhRAXJkLqdnJMLjCfFSjq8ovjhlNcuDPk8N/mYlA2845PGgi74Kb/zCG1WH8NtFK06xrpn15KyUxSANxoQ6C9QnzE9sc4aZj5rUatWeekvBfbqngq3JpG
6 - PitP2MuX4/Yysso8dCl1h2VK3MKoU2YpyzvLgZ3hZX/cBzSWp9O0Eafzj6GIMvAGVaL0x0V+K2Wv4eBOLIhDczhJXvHmKvTU7ZJnAwI37JXkOecN4HJdAXfFqg2WkT5f
7 - 1Mj8WnSqgLE08qfeYC1a3nZQ1jszxbT9J+ClUy8rCYusZHiArQcCgCwrNbWbI2yVfRjYOpsuTgyq31fnuHrkVfGu6RhiRhucR0a0Dign5fSU71STKksweHQ+oYQJibnQ
8 - TgGDGlOFWyfKO50xxPTPOmSpEsmpIVtWfnXkhhAoRsbZwo6z4oAuBJQs8EibsOr/r8KY5UHRbp+q3SlDhBE3mWszybMdOVRQKyJ1lZVXpmxmjXp/W2AqitsjCTKQaHi+
9 - 4xUnNjT8P0WiPtYg6ojrrQZnF0gU0wnndNQdLfPOMxoDvWjfe5OuEcY55yDRIosdpkeItTMVN1CRL4WecFgM8mBIVlnssE4Q1GM87PWNHipGZ91+MJwdsr0yUfCsJyRv
By the way you are using a 16 byte key and get AES-128 not AES-256.
As Mark pointed out, ObjectOutputStream creates an object header, so the common prefix is because of that and because you're not using a salt and you're using the same encryption key.
These weaknesses make the encryption solution (i.e. your code) susceptible to ciphertext-only attacks, even though the algorithm itself is perfectly fine. You've just implemented it in an unsecure way.
With the following Code - I'm generally able to decrypt a token that I'm being passed. The token string when decoded to json will look like the following:
\"id\":\"9efef759-15a3-4cd0-b1f1-fceab7ad0a6e\",
\"exp\":\"2016-07-23T15:27:50.758+12:00\",
\"iv\":\"OOqNpy9puM5jPjTwrWHSNb+d5NYDEwIq2pZFqx6mraI14Kkh0bzEWADoU2d/KGu6cp9/FrVt4epheIP5Fw9qUFrdVcNYjLO5HWdJ0V5GhpdLJlFbMnFy4vS1rJ+4X1qTNZrqPwZh2deLceoHmxnqw7ml8JVFeIaz9H8BQXkgcNo=\",
\"ver\":\"1\",
\"iat\":\"2016-07-13T15:27:50.758+12:00\",
\"key\":\"d7R9blmqBYMywOEdYpRbd+gvKPfOqmxsRQMlDipkuGoWZobJ0dnK0MGBFAXq4wOdHbHVbfisjqm+6HoRSZ2w0KcfY+enPoKL5yptvlULkwpDtATEP8pnRmCh6ycWntbanL1gJI7RoNWTkomItBp/yODdL5kSMue76xAtIzc9+no=\",
\"sig\":\"X6A58tRDSUC5HJEP1VVmQjo17Qk2rJC9pYZiV5ccIjdcLmz7HPIkpm0ZCsFcQX4ps1k32asSojqOyegYFIdDqHypdrV9c5sHchIrp6Ak8MOjNTpy+SweTGPzkjlEHCMkWLVHjrkBq9mmoMk2o0sYyZes+/ARuYB8IjtAINtbAQE=\",
\"enc\":\"n+exbDhicBLuUtbYPXrrKESIktgyaidSreD5FWAxErGJeOyjTWv9QOqCGfEou5yJq2njCddf0mu0JOEP9i1mlhe1MUUa1hE4J+qnqxre+tSxWRNszHQL8Pk+0FV6cZ1nqk+aCfw9VOjlOLYXYmNF0NSZBqQIqzpobM3twHIf5u7pvJkvbnfP8Db0S83ZchNgMWyH1t+UEb+jbpcg1Um3U7Yb8Q==\"
The IV and key are pulled from the token and Asymmetrically decrypted then the enc text is symmetrically decrypted before they are passed to a gzip decompression.
internal virtual UserObj decrypt(string jsonToken, UserObj cls, System.Security.Cryptography.AsymmetricAlgorithm certPrivateKey)
{
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair bcPrivateKey;
try
{
//Make a bouncyCastle private key for feeding to the rsa Engine.
bcPrivateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(certPrivateKey);
// Attempt to unmarshal the JSON token
Token token = encoder.unmarshalJsonString<Token>(jsonToken);
// Asymmetrically encrypt the symmetric encryption iv
Org.BouncyCastle.Crypto.Engines.RsaEngine rsaCipher = new Org.BouncyCastle.Crypto.Engines.RsaEngine();
Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding rsaEncoder = new Org.BouncyCastle.Crypto.Encodings.Pkcs1Encoding(rsaCipher);
rsaEncoder.Init(false, bcPrivateKey.Private);
// Asymmetrically decrypt the symmetric encryption key
byte[] encryptedAesKeyBytes = encoder.fromBase64String(token.Get(Token.ENCRYPTED_KEY));
byte[] aesKeyBytes = rsaEncoder.ProcessBlock(encryptedAesKeyBytes, 0, encryptedAesKeyBytes.Length);
// Asymmetrically decrypt the symmetric encryption IV
byte[] encryptedAesIvBytes = encoder.fromBase64String(token.Get(Token.IV));
byte[] aesIvBytes = rsaEncoder.ProcessBlock(encryptedAesIvBytes, 0, encryptedAesIvBytes.Length);
//Setting equivalent excyption to "AES/CTR/NoPadding"
Org.BouncyCastle.Crypto.Engines.AesEngine aes = new Org.BouncyCastle.Crypto.Engines.AesEngine();
Org.BouncyCastle.Crypto.Modes.SicBlockCipher blockCipher = new Org.BouncyCastle.Crypto.Modes.SicBlockCipher(aes);
Org.BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher aesCipher = new Org.BouncyCastle.Crypto.Paddings.PaddedBufferedBlockCipher(blockCipher, new Org.BouncyCastle.Crypto.Paddings.ZeroBytePadding());
Org.BouncyCastle.Crypto.Parameters.KeyParameter keyParam2 = new Org.BouncyCastle.Crypto.Parameters.KeyParameter(aesKeyBytes);
// Symmetrically decrypt the data
Org.BouncyCastle.Crypto.Parameters.ParametersWithIV keyParamWithIv = new Org.BouncyCastle.Crypto.Parameters.ParametersWithIV(keyParam2, aesIvBytes, 0, TokenEncryptor.IV_SIZE_BYTES);
//
// Symmetrically decrypt the data
aesCipher.Init(false, keyParamWithIv);
string encryptedData = token.Get(Token.ENCRYPTED_DATA);
byte[] inputBytes = encoder.fromBase64String(encryptedData);
byte[] compressedJsonBytes = new byte[aesCipher.GetOutputSize(inputBytes.Length)];
//Do the decryption. length is the proper size of the compressed data, compressedJsonBytes will
//contain extra nulls at the end.
int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);
//String to look at the compressed data (debug)
string compressed = encoder.toBase64String(compressedJsonBytes);
byte[] compressedJsonBytesProperSize = new byte[length];
Array.Copy(compressedJsonBytes, compressedJsonBytesProperSize, length);
//String to look at the compressed data (debug)
compressed = encoder.toBase64String(compressedJsonBytesProperSize);
byte[] jsonBytes = null;
try
{
jsonBytes = encoder.decompress(compressedJsonBytesProperSize);
}
catch (Exception)
{
jsonBytes = encoder.decompress(compressedJsonBytes);
}
string tmep = System.Text.Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length);
UserObj dataObj = encoder.fromJsonBytes<UserObj>(jsonBytes);
return dataObj;
}
catch (Exception e)
{
throw new Exceptions.TokenDecryptionException(e);
}
}
The final decrypted token looking something like this:
\"domain\":\"GLOBAL\",
\"user\":\"someuser\",
\"groups\":[\"GROUP1\",\"GROUP2\",\"GROUP3\"],
\"branchId\":\"0000\"
My problem occurs when depending on the number of items in the group, the GZ Decompression will fail. On some tokens if I pass the full compressedJsonByte array (with nulls at the end), it complains about a CRC error (why I have the try/catch around the decompression), so then I pass it the trimmed byte array. But for other tokens with more groups, it decompresses with the full byte array.
I have a comparable encryption routine and found that with everything else being equal if a alerted the user name from 17 to 19 characters I'd need to use the untrimmed byte array for decompression. But have since found the problem runs deeper.
Any help would be appreciated. I'm hoping its a decompression issue, but I suspect maybe something in the decrypting is fudging the end of the output byte array.
I'm not able to change the decryption type as it's coming from an external entity and their side is written in Java.
For reference the decompression routine is:
public virtual byte[] decompress(byte[] compressedData)
{
try
{
//Push to a file for debug
System.IO.FileStream fs = new System.IO.FileStream(#"C:\temp\file.gz", System.IO.FileMode.OpenOrCreate);
fs.Write(compressedData,0,compressedData.Length);
fs.Flush();
fs.Close();
byte[] outputBytes = new byte[4096];
byte[] buffer = new byte[4096];
using (System.IO.MemoryStream msInput = new System.IO.MemoryStream(compressedData))
{
System.IO.MemoryStream msOutput = new System.IO.MemoryStream();
//using (System.IO.Compression.GZipStream gzs = new System.IO.Compression.GZipStream(msInput, System.IO.Compression.CompressionMode.Decompress))
using (ZLibNet.GZipStream gzs = new ZLibNet.GZipStream(msInput, ZLibNet.CompressionMode.Decompress))
{
int nRead;
bool canR = gzs.CanRead;
while ((nRead = gzs.Read(buffer, 0, buffer.Length)) > 0)
{
msOutput.Write(buffer, 0, nRead);
}
}
outputBytes = msOutput.ToArray();
if (outputBytes.Length == 0)
throw new Exception("Could not decompress");
}
return outputBytes;
}
catch (Exception e)
{
throw new Exceptions.ServiceException(e);
}
}
You're using ZeroBytePadding while CTR mode doesn't require any padding at all, you might as well directly use the SicCipher instance.
Zero byte padding will strip all the zero valued bytes from the end of the data, which is why you may end up with compromised data.
Zero byte padding is not deterministic and should not be used unless you're either:
sure of the data not ending with a zero byte or bit;
able to determine the plaintext length in other ways.
I may have just cracked it. For the larger payload token, ProcessBytes would work in one pass.
For the smaller ones it took me a long while to notice that the length reported from ProcessBytes was less that the length of the inputBytes array.
I tried a DoFinal after the process bytes:
int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);
int length2= aesCipher.DoFinal(compressedJsonBytes, length);
And for a token I encoded it worked...but for the given ones I have, it posted a Org.BouncyCastle.Crypto.DataLengthException: last block incomplete in decryption
So finally I tried
int length2 = aesCipher.ProcessBytes(inputBytes,length,inputBytes.Length-length,compressedJsonBytes,length);
To process the remain data - and this worked.
So my resulting code now looks as follows - replacing the single line:
int length = aesCipher.ProcessBytes(inputBytes, compressedJsonBytes, 0);
With:
int length = 0;
while (length < inputBytes.Length)
{
length += aesCipher.ProcessBytes(inputBytes, length, inputBytes.Length-length, compressedJsonBytes, length);
}
And this seems to have removed the need to take the full length compressed byte array. The UncompressedProperSize byte array now down it's job properly.
I need to access some data that used PHP encryption. The PHP encryption is like this.
base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($cipher), $text, MCRYPT_MODE_ECB));
As value of $text they pass the time() function value which will be different each time that the method is called in. I have implemented this in Java. Like this,
public static String md5(String string) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Huh, MD5 should be supported?", e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Huh, UTF-8 should be supported?", e);
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
int i = (b & 0xFF);
if (i < 0x10) hex.append('0');
hex.append(Integer.toHexString(i));
}
return hex.toString();
}
public static byte[] rijndael_256(String text, byte[] givenKey) throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException{
final int keysize;
if (givenKey.length <= 192 / Byte.SIZE) {
keysize = 192;
} else {
keysize = 256;
}
byte[] keyData = new byte[keysize / Byte.SIZE];
System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
KeyParameter key = new KeyParameter(keyData);
BlockCipher rijndael = new RijndaelEngine(256);
ZeroBytePadding c = new ZeroBytePadding();
PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);
pbbc.init(true, key);
byte[] plaintext = text.getBytes(Charset.forName("UTF8"));
byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];
int offset = 0;
offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
offset += pbbc.doFinal(ciphertext, offset);
return ciphertext;
}
public static String encrypt(String text, String secretKey) throws Exception {
byte[] givenKey = String.valueOf(md5(secretKey)).getBytes(Charset.forName("ASCII"));
byte[] encrypted = rijndael_256(text,givenKey);
return new String(Base64.encodeBase64(encrypted));
}
I have referred this answer when creating MCRYPT_RIJNDAEL_256 method."
Encryption in Android equivalent to php's MCRYPT_RIJNDAEL_256
"I have used apache codec for Base64.Here's how I call the encryption function,
long time= System.currentTimeMillis()/1000;
String encryptedTime = EncryptionUtils.encrypt(String.valueOf(time), secretkey);
The problem is sometimes the output is not similar to PHP but sometimes it works fine.
I think that my MCRYPT_RIJNDAEL_256 method is unreliable.
I want to know where I went wrong and find a reliable method so that I can always get similar encrypted string as to PHP.
The problem is likely to be the ZeroBytePadding. The one of Bouncy always adds/removes at least one byte with value zero (a la PKCS5Padding, 1 to 16 bytes of padding) but the one of PHP only pads until the first block boundary is encountered (0 to 15 bytes of padding). I've discussed this with David of the legion of Bouncy Castle, but the PHP zero byte padding is an extremely ill fit for the way Bouncy does padding, so currently you'll have to do this yourself, and use the cipher without padding.
Of course, as a real solution, rewrite the PHP part to use AES (MCRYPT_RIJNDAEL_128), CBC mode encryption, HMAC authentication, a real Password Based Key Derivation Function (PBKDF, e.g. PBKDF2 or bcrypt) and PKCS#7 compatible padding instead of this insecure, incompatible code. Alternatively, go for OpenSSL compatibility or a known secure container format.
So I have these large files (6GB+) that I need to decrypt on a 32 bit computer. The general procedure that I used previously was to read the entire file in memory, then pass it on to the decrypt function and then write it all back to a file. This doesn't really work due to memory limitations. I did try passing the file in parts to the decrypt function but it seems to mess up around the boundaries of where I break up the file before sending it to the decrypt function.
I've tried breaking up the file in parts relative to key size but that doesnt seem to matter. I tried a byte array of size 2048 as well as a byte aray of size 294 thinking that might be the special boundary but, no luck. I can see parts of the file correctly decrypted but parts which are total gibberish.
Is it just NOT POSSIBLE to decrypt the file in chunks? If there is a way, then how?
Here is my decryption function / my attempt to decrypt in parts.
private Path outFile;
private void decryptFile(FileInputStream fis, byte[] initVector, byte[] aesKey, long used) {
//Assume used = 0 for this function.
byte[] chunks = new byte[2048]; //If this number is greater than or equal to the size of the file then we are good.
try {
if (outFile.toFile().exists())
outFile.toFile().delete();
outFile.toFile().createNewFile();
FileOutputStream fos = new FileOutputStream(outFile.toFile());
OutputStreamWriter out = new OutputStreamWriter(fos);
IvParameterSpec spec = new IvParameterSpec(Arrays.copyOfRange(initVector, 0, 16));
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, spec);
int x;
while ((x = fis.read(chunks, 0, chunks.length)) != -1) {
byte[] dec = cipher.doFinal(Arrays.copyOfRange(chunks, 0, x));
out.append(new String(dec));
}
out.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
LOG.error(ExceptionUtils.getStackTrace(e));
}
}
Consider using Cipher#update(byte[], int, int, byte[], int) instead of doFinal() for multipart operations. This will take care of part boundaries for you.
The last part of the deciphered data can be obtained by calling the doFinal(byte[] output, int outputOffset) method.
String plain1= "Test";
byte[] cipher = SplashSecure.getInstance().encrypt2(plain1);
String plain2 = SplashSecure.getInstance().decrypt2(cipher);
plain = Test������������������������
After decryption plainText2 should be equal to plaintext. But it's not.
Encrypt/Decrypt methods.
public void initKey(String key) {
String paddedKey = Utils.padString(key);
mKeyspec = new SecretKeySpec(Utils.getBytes(paddedKey), "AES/ECB/NoPadding");
// Utils.getBytes returns "paddedKey.getBytes("CP1252")"
}
public byte[] encrypt2(String data) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, mKeyspec);
String paddedData = Utils.padString(data);
return cipher.doFinal(Utils.getBytes(paddedData));
} catch(InvalidKeyException e) {
e.printStackTrace();
// Series of catch blocks
}
return null;
}
public String decrypt2(byte[] cypherText) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, mKeyspec);
byte[] plainTextBytes = cipher.doFinal(cypherText);
return Utils.getString(plainTextBytes);
// Utils.getString returns "new String(bytes, "CP1252");"
} catch(InvalidKeyException e) {
// Series of catch blocks.
}
return null;
}
Edit
public static String padString(String source) {
char paddingChar = '\0';
int size = 16;
int padLength = size - source.length() % size;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
Edit
I'm trying to get the encryption-decryption working across Windows (other client that encrypts, and server) and Android. The Windows client is a VC++ app that uses a Rijndael class (http://svn.openfoundry.org/pcman/2007.06.03/Lite/Rijndael.h) and Android uses http://www.cs.ucdavis.edu/~rogaway/ocb/ocb-java/Rijndael.java. The Windows client has encrypted the data and stored it on the server. I need to build a client for android that fetches the encrypted data, decrypt it and display to the user.
I'm sure I'm using the correct key to decrypt.
AES has a block size of 128 bits (i.e 16 bytes). It can only process data in blocks of this size, so even though you have told it to use NoPadding it is unable to comply.
The most likely thing that is happening here is that the AES implementation you are using is internally padding your four bytes of input up to 16 bytes and encrypting the result. When you decrypt, you get the same 16 bytes back out, i.e. 'T', 'e', 's', 't' and 12 garbage bytes.
The output you see supports this: "Test" followed by 24 ? symbols. I don't know why it's printing two ? symbols for each garbage byte, but I'm guessing it's something to do with interpreting the garbage bytes in unicode. You could see what is going on by printing out the raw byte values of the decrypted blob.
The short answer is that 'NoPadding' doesn't make sense for a block cipher (or, rather, if you are going to use NoPadding then you have to pad and unpad things yourself).