AES Encryption and decryption for android and iOS gives wrong output - android

Android code:
private static final String ALGORITHM = "AES/ECB/PKCS5Padding";
private static final String KEY = "SixteenCharacter";
Encryption:
private static byte[] encrypt(final byte[] key, final byte[] originalData) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(originalData);
return encrypted;
} catch (Exception ex) {
ex.printStackTrace();
return new byte[0];
}
}
Decryption:
private static byte[] decrypt(final byte[] key, final byte[] encryptedData) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec skeySpec = new SecretKeySpec(key, ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] original = cipher.doFinal(encryptedData);
return original;
} catch (Exception ex) {
ex.printStackTrace();
return new byte[0];
}
}
iOS code:
static let KEY = "SixteenCharacter"
private static let iv: Array = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
Encryption:
static func getEncryptedString(normal: String?, key: String, options: Int = kCCOptionPKCS7Padding) -> String {
guard let normal = normal else { return "" }
if let keyData = key.data(using: String.Encoding.utf8),
let data = normal.data(using: String.Encoding.utf8),
let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {
let keyLength = size_t(kCCKeySizeAES128)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(options)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(keyData as NSData).bytes, keyLength,
iv,
(data as NSData).bytes, data.count,
cryptData.mutableBytes, cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
var base64cryptString = cryptData.base64EncodedString(options: .lineLength76Characters)
//base64cryptString = base64cryptString.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "+", with: "-")
return base64cryptString
}
else {
return normal
}
}
return normal
}
Decryption:
static func getDecryptedString(normal: String?, key: String, options: Int = kCCOptionPKCS7Padding) -> String {
guard var normal = normal else { return "" }
//normal = normal.replacingOccurrences(of: "_", with: "/").replacingOccurrences(of: "-", with: "+")
if let keyData = key.data(using: .utf8),
let data = NSData(base64Encoded: normal, options: .ignoreUnknownCharacters),
let cryptData = NSMutableData(length: Int((data.length)) + kCCBlockSizeAES128) {
let keyLength = size_t(kCCKeySizeAES128)
let operation: CCOperation = UInt32(kCCDecrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(options)
var numBytesEncrypted :size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(keyData as NSData).bytes, keyLength,
iv,
data.bytes, data.length,
cryptData.mutableBytes, cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
let unencryptedMessage = String(data: cryptData as Data, encoding:String.Encoding.utf8)
if let unencryptedMessage = unencryptedMessage, !unencryptedMessage.isEmpty {
return unencryptedMessage
}
}
else {
return normal
}
}
return normal
}
As I haven't worked with AES encryption before, I have no idea where am I going wrong
I have seen so many solutions on the internet but no luck yet
Android code is already done and the only way I have is to modify the iOS code!
Thanks in advance!

You are working across two different systems. That means you cannot rely on system defaults, they may be different. You need to specify everything: Key, IV, padding, character encoding, etc. Crypto is designed to fail with even a small mismatch, so you have to check everything to make sure it matches across both systems. For instance, IIRC iOS and Java use different end-of-line characters. Something as simple as that might throw off the decryption.
If there is nothing obvious then look at all the inputs to both sides in binary/hex to make sure everything matches exactly.
You are using ECB mode. This is insecure and shouldn't be used except for very specific and rare situations. Better to use CBC or CTR mode. If you need authentication as well then try GCM mode.

Related

How to encrypt data in android same way as done in angular using crypto-js

Code in angular using crypto-js:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(data),
secret_key,
{
iv: secret_iv,
padding: CryptoJS.pad.ZeroPadding
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
I am not able to do same encryption in android.
Android Code
String key32Char = "12345123451234512345123451234509";
String iv32Char = "12345123451234512345123451234509";
byte[] srcBuff = jsonString.getBytes("UTF-8");
//SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char.getBytes(), "AES");
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decode(key32Char, Base64.NO_WRAP), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decode(iv32Char, Base64.NO_WRAP));
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = Base64.encodeToString(dstBuff, Base64.NO_WRAP);
JSONObject requestObj = new JSONObject();
requestObj.put("input", encryptedString);
What CryptoJS.enc.Hex.parse(key) line does ?
How to do same encryption ?
IV and Key:To match the key and IV part both must use either base64 or hex decodings.
In Hex encoded string there are 32 hex char that makes 128-bit. However, the same string can be rejected by base64 decode and if not rejected the output will not be 128-bit. You need to use
byte[] bytes = new BigInteger("7F" + str, 16).toByteArray();
SecretKeySpec key = new SecretKeySpec(bytes, 1, bytes.length-1, "AES");
to convert the hex string into byte array.
padding: CryptoJS.pad.ZeroPadding is useful if your data size is an exact multiple of 128. Otherwise, you need to use this parameter to say that I'll use this for testing my new padding scheme. You need to better use Pkcs7 that was the default.
In Java you need getInstance("AES/CBC/PKCS5Padding");
Mode of operation: The default in JS is CBC, therefor you need the same in Java, as above getInstance("AES/CBC/PKCS5Padding");
Output: To compare the outputs you need to see the same result. In Java you convert the output into base64, so you need the same for JS.
As you can see, you must do the same steps, parameters for both.
Note that: CBC mode is archaic and you should prefer authenticated encryption modes like AES-GCM or ChaCha20-Poly1305. They not only provides confidentiality but also integrity and authentication. Unfortunately, crypto-js doesn't have them. But you can use some other JS libraries for that.
As name suggests, CryptoJS.enc.Hex.parse(key) parses a Hex String and uses it as key. So you need to do the same for your java code.
In addition, you need to select correct encryption mode and padding too. Your CryptoJs code uses CBC mode so you need to do same in Java Code. Your are using zero padding in CryptoJs side which is not available in java, so you need to do it manually. But in general, using zero padding is a bad idea and it is better to use PKCS5 padding for example which is default for CryptoJs.
With these things, these 2 codes match:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
"0123456789012345x",
secret_key,
{
iv: secret_iv,
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
Java:
public void doit()
{
byte[] key32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] iv32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] srcBuff = "0123456789012345x".getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = new String(Base64.getEncoder().encode(dstBuff));
System.out.print(encryptedString);
}
catch(Exception e) {
System.out.print(e.toString());
return;
}
}
public byte[] hexStringToByteArray(String s)
{
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
Update:
If you are forced to use bad idea of zero padding, you need to keep real size of data and do padding manually:
public void doitZeroPadding()
{
...
// For the simplicity, I assume that data size is smaller than 128.
// You need to change this part as needed.
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int dsize = srcBuff.length + 1; // 1 is for plain buffer size
// This line align size to the multiple of block size.
int newBufSize = ((dsize + cipher.getBlockSize() - 1) / cipher.getBlockSize()) * cipher.getBlockSize();
byte[] newSrcBuf = new byte[newBufSize];
// You need real buffer size, or you don't know how long is decrypted buffer.
// I add it inside encrypting buffer to prevent other to see real decrypted buffer size.
// But if you want to have exact same encrypted buffer on both sides, you must remove it.
newSrcBuf[0] = (byte)(srcBuff.length);
System.arraycopy(srcBuff, 0, newSrcBuf, 1, srcBuff.length);
// Now use newSrcBuf/newBufSize
...
}
on the decryption side, check real size from decrypted buffer and use that size starting byte 1 for creating string.

Swift AES Encryption throws error while in Android doesn't

I tried to do AES encryption in Swift which I do in Android like this:
public static String Encrypt(String text, String key) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] keyBytes = new byte[16];
byte[] b = key.getBytes("UTF-8");
int len = b.length;
if (len > keyBytes.length)
len = keyBytes.length;
System.arraycopy(b, 0, keyBytes, 0, len);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(keyBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
byte[] results = cipher.doFinal(text.getBytes("UTF-8"));
return Base64.encodeToString(results, Base64.DEFAULT);
}
catch (Exception ex){return "error"+ex.getMessage();}
}
Below is the equivalent code in Swift 3.2:
func aesEncrypt(key: String, iv: String) throws -> String{
let data = self.data(using: String.Encoding.utf8)
let enc = try AES(key: key.bytes, blockMode: BlockMode.CBC(iv: iv.bytes, padding: Padding.pkcs5).encrypt(data!.bytes)
let encData = NSData(bytes: enc, length: Int(enc.count))
let base64String: String = encData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0));
let result = String(base64String)
return result!}
In Android it doesn't matter for key: I can fill with any string (no length limitations). But when using Swift I have to use a 32 charachter string for key and a 16 charachter string for IV, otherwise it will throw an error.
Here is the Swift usage:
let data = "this is string which I want to be encrypted"
let key = "bbbb98232-a343-4343f-2111"
let iv = "0000000000000000" // lenght = 16 like android code
let encryptedString = data.aesEncrypt(key: key, iv: iv);
Is there maybe some mistake in my Swift code?
You can try the below Swift code for AES encryption. Its String extension.
import Foundation
import CommonCrypto
extension String {
func aesEncrypt(key: String, initializationVector: String, options: Int = kCCOptionPKCS7Padding) -> String? {
if let keyData = key.data(using: String.Encoding.utf8),
let data = self.data(using: String.Encoding.utf8),
let cryptData = NSMutableData(length: Int((data.count)) + kCCBlockSizeAES128) {
let keyLength = size_t(kCCKeySizeAES128)
let operation: CCOperation = UInt32(kCCEncrypt)
let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
let options: CCOptions = UInt32(options)
var numBytesEncrypted: size_t = 0
let cryptStatus = CCCrypt(operation,
algoritm,
options,
(keyData as NSData).bytes, keyLength,
initializationVector,
(data as NSData).bytes, data.count,
cryptData.mutableBytes, cryptData.length,
&numBytesEncrypted)
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.length = Int(numBytesEncrypted)
let base64cryptString = cryptData.base64EncodedString(options: .lineLength64Characters)
return base64cryptString
} else {
return nil
}
}
return nil
}
}

AES 256 CBC encryption in Laravel and Decryption in android

So my problem is this: i have a password that i'm encrypting in Laravel 5.6 with AES-256-CBC and send it to an android device, problem is i can't find a way to decrypt it knowing that i found a way to extract the IV and the encrypted value and the key is available on the android device !
I'm successfully decrypting the value if i use AES-128-CBC using this code on the android device, but failing the AES-256-CBC cypher and i don't understand where the problem is !
The code :
public static String decrypt(byte[] keyValue, String ivValue, String encryptedData) throws Exception {
Key key = new SecretKeySpec(keyValue, "AES");
byte[] iv = Base64.decode(ivValue.getBytes("UTF-8"), Base64.DEFAULT);
byte[] decodedValue = Base64.decode(encryptedData.getBytes("UTF-8"), Base64.DEFAULT);
Cipher c = Cipher.getInstance("AES/CBC/PKCS7Padding");
c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decValue = c.doFinal(decodedValue);
return new String(decValue);
}
At what instance it's specified that this code should use AES-128 and not 256 ? and how can i change it !
Thanks in advance !
EDIT
the PHP code is as follows :
$cipher="AES-256-CBC";
$key='somerandomkeyof32byteslong';
$crypt=new Encrypter($key,$cipher);
$result=$crypt->encryptString('oussama');
//i'm sending the result to the android device
Try this one
Security.java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Security {
public static String encrypt(String input, String key){
byte[] crypted = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skey);
crypted = cipher.doFinal(input.getBytes());
}catch(Exception e){
System.out.println(e.toString());
}
return new String(Base64.encodeBase64(crypted));
}
public static String decrypt(String input, String key){
byte[] output = null;
try{
SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skey);
output = cipher.doFinal(Base64.decodeBase64(input));
}catch(Exception e){
System.out.println(e.toString());
}
return new String(output);
}
public static void main(String[] args) {
String key = "1234567891234567";
String data = "example";
System.out.println(Security.decrypt(Security.encrypt(data, key), key));
System.out.println(Security.encrypt(data, key));
}
}
Security.php
class Security {
public static function encrypt($input, $key) {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = Security::pkcs5_pad($input, $size);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
private static function pkcs5_pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
public static function decrypt($sStr, $sKey) {
$decrypted= mcrypt_decrypt(
MCRYPT_RIJNDAEL_128,
$sKey,
base64_decode($sStr),
MCRYPT_MODE_ECB
);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
}?>
Example.php
<?php
include 'security.php';
$value = 'plain text';
$key = "your key"; //16 Character Key
echo "Encrypt =>"."<br><br>";
echo Security::encrypt($value, $key);
echo "<br><br>"."Decrypt =>"."<br><br>";
echo Security::decrypt("AES Encrypted response",$key);
//echo Security::decrypt(Security::encrypt($value, $key), $key);
?>
If you need AES with 256 bit key length, you can do it like this:
Cipher c = Cipher.getInstance("AES_256/CBC/PKCS7Padding");
Android reference sometimes better than oracle when you want to use java classes for android. Here is reference.
But remember that is only api 26+. You can compile openssl and use it in an JNI if you need support for previous versions(and I think you need to do). or find another cryptographic library for java.

How to decrypt data sent from Android (Java) using Node.Js

I have run into a problem - an existing program uses the code below to encrypt data
public static String encryptData(String data, final String key) {
try {
byte[] kb=key.getBytes("utf-8");
byte[] dig= MessageDigest.getInstance("SHA-1").digest(kb);
byte[] kdig= Arrays.copyOf(dig, 24);
final SecretKeySpec secretKeySpec = new SecretKeySpec(kdig, "DESede");
final Cipher instance = Cipher.getInstance("DESede");
instance.init(ENCRYPT_MODE, secretKeySpec);
byte[] ecb=instance.doFinal(data.getBytes("utf-8"));
byte[] enb64=Base64.encode(ecb, Base64.DEFAULT);
return new String(enb64);
} catch (Exception ex) {
ErmsLogger.e(TAG, ex.getMessage());
return null;
}
}
I need to write code on Node.Js that decrypts this encrypted data. So far - I have come up with
function decryptData(encrpted_data,fn){
var hashedKey = crypto.createHash('sha1').update(config.dataPassword).digest('hex');
if(hashedKey.length < 48){
var num=48 - hashedKey.length;
for(var i=0;i < num; i++){
hashedKey +='0';
}
}
var key=Buffer.from(hashedKey, 'hex');
var decipher = crypto.createDecipher('des-ede', key);
decoded = decipher.update(encrpted_data, 'base64', 'utf8');
decoded += decipher.final('utf8');
log.debug(JSON.stringify(decoded));
return fn(decoded);
}
I keep on running into
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
at Decipher.final (internal/crypto/cipher.js:104:26)
whenever I try to decrypt data sent from the android app.
the working decryption code on Android (Java) is
public static String decryptData(final String data, final String password) throws Exception {
MessageDigest instance=MessageDigest.getInstance("SHA-1");
byte[] passworddigest=instance.digest(password.getBytes("utf-8"));
byte[] key=Arrays.copyOf(passworddigest, 24);
final SecretKeySpec secretKeySpec = new SecretKeySpec(key, "DESede");
final byte[] decodeddata = Base64.decode(data.getBytes("utf-8"), Base64.DEFAULT);
final Cipher ciperInstance = Cipher.getInstance("DESede");
ciperInstance.init(DECRYPT_MODE, secretKeySpec);
byte[] res= ciperInstance.doFinal(decodeddata);
return new String(res, "UTF-8");
}
Could you assist me translate this in Node?
After a bit of research and hitting my head against it - I finally came up with something that works
function decryptData(encrpted_data,fn){
var encodeKey = crypto.createHash('sha1').update(config.dataPassword).digest('hex');
var cryptkey=Buffer.alloc(24);
encodeKey.copy(cryptkey);
var decipher = crypto.createDecipheriv('des-ede3', cryptkey,'');
decipher.setAutoPadding(true);
decoded = decipher.update(encrpted_data, 'base64', 'utf8');
decoded += decipher.final('utf8');
log.debug(JSON.stringify(decoded));
return fn(decoded);
}
the explanation -
crypto.createDecipher() - internally uses the MD5 hash for the key passed to it and so it really doesn't matter what key value you send in - it will be changed. So the best option is to use createDecipheriv which accepts a raw key. This allows you to hash your key outside before passing it in. Since I was not using any IV - I passed the value of ''.
In Java (Android) DESede is really TripleDES and the equivalent on on crypto is 'des-ede3' and not 'des-ede'.
I hope this saves someone a couple of hours.

CryptographicException: Bad PKCS7 padding

I am seeing a small percentage of production users randomly report this exception related to encrypting/decrypting strings with Xamarin.Android but unfortunately I cannot reproduce it.
What could cause this and/or how could I reproduce the exception so that I can figure out a fix/workaround?
[CryptographicException: Bad PKCS7 padding. Invalid length 147.]
Mono.Security.Cryptography.SymmetricTransform.ThrowBadPaddingException(PaddingMode padding, Int32 length, Int32 position):0
Mono.Security.Cryptography.SymmetricTransform.FinalDecrypt(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
Mono.Security.Cryptography.SymmetricTransform.TransformFinalBlock(System.Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount):0
System.Security.Cryptography.CryptoStream.FlushFinalBlock():0
com.abc.mobile.shared.Security+PasswordEncoder.DecryptWithByteArray(System.String strText, System.String strEncrypt):0
EDIT: Here's the code I am using to encrypt/decrypt
private string EncryptWithByteArray(string inPassword, string inByteArray)
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes(inByteArray.Substring(0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputArray = System.Text.Encoding.UTF8.GetBytes(inPassword);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write(inputArray, 0, inputArray.Length);
cs.FlushFinalBlock();
return Convert.ToBase64String(ms.ToArray());
}
private string DecryptWithByteArray (string strText, string strEncrypt)
{
try
{
byte[] tmpKey = new byte[20];
tmpKey = System.Text.Encoding.UTF8.GetBytes (strEncrypt.Substring (0, 8));
DESCryptoServiceProvider des = new DESCryptoServiceProvider ();
Byte[] inputByteArray = Convert.FromBase64String (strText);
MemoryStream ms = new MemoryStream ();
CryptoStream cs = new CryptoStream (ms, des.CreateDecryptor (tmpKey, mInitializationVector), CryptoStreamMode.Write);
cs.Write (inputByteArray, 0, inputByteArray.Length);
try {
cs.FlushFinalBlock();
} catch (Exception ex) {
throw(ex);
}
System.Text.Encoding encoding = System.Text.Encoding.UTF8;
return encoding.GetString(ms.ToArray());
}
catch (Exception ex)
{
throw ex;
}
}
EDIT 2:
The encryption key is always the local Device ID. Here's how I am getting this:
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId == null ? "UNAVAILABLE" : telephonyMgr.DeviceId;
Here's an example of how it's called:
string mByteArray = GetDeviceId();
string mEncryptedString = EncryptWithByteArray(stringToEncrypt, mByteArray);
string mDecryptedString = DecryptWithByteArray(mEncryptedString, mByteArray);
You have not provided much details about your use case but I would say this is happening because you are not using the same cipher settings during the encryption and decryption operations. Symmetric ciphers require you to use exactly the same settings/parameters during the data encryption and also decryption. For example for AES CBC you would need to use exactly the same key, IV, cipher mode and padding on both devices. It is best to set these setting explicitly in the code:
System.Security.Cryptography.RijndaelManaged aes = new System.Security.Cryptography.RijndaelManaged();
aes.Key = new byte[] { ... };
aes.IV = new byte[] { ... };
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
If you are sure you are using the same settings then you should also consider scenario that some data get corrupted or altered during the network transfer.
Edit after some code fragments have been provided:
Decryption method you have provided does not work for me at all so I have put together all your samples and turned them into the code which does the same thing as yours but uses IMO a slightly cleaner approach. For example this code uses more robust "key derivation" (please forgive me cryptoguys) and it has also passed basic code analysis.
You should be able to easily use public methods to do what you need:
string plainData = "This information should be encrypted";
string encryptedData = EncryptStringified(plainData);
string decryptedData = DecryptStringified(encryptedData);
if (plainData != decryptedData)
throw new Exception("Decryption failed");
Implementation and private methods follows:
/// <summary>
/// Encrypts string with the key derived from device ID
/// </summary>
/// <returns>Base64 encoded encrypted data</returns>
/// <param name="stringToEncrypt">String to encrypt</param>
public string EncryptStringified(string stringToEncrypt)
{
if (stringToEncrypt == null)
throw new ArgumentNullException("stringToEncrypt");
byte[] key = DeviceIdToDesKey();
byte[] plainData = Encoding.UTF8.GetBytes(stringToEncrypt);
byte[] encryptedData = Encrypt(key, plainData);
return Convert.ToBase64String(encryptedData);
}
/// <summary>
/// Decrypts Base64 encoded data with the key derived from device ID
/// </summary>
/// <returns>Decrypted string</returns>
/// <param name="b64DataToDecrypt">Base64 encoded data to decrypt</param>
public string DecryptStringified(string b64DataToDecrypt)
{
if (b64DataToDecrypt == null)
throw new ArgumentNullException("b64DataToDecrypt");
byte[] key = DeviceIdToDesKey();
byte[] encryptedData = Convert.FromBase64String(b64DataToDecrypt);
byte[] decryptedData = Decrypt(key, encryptedData);
return Encoding.UTF8.GetString(decryptedData);
}
private byte[] DeviceIdToDesKey()
{
TelephonyManager telephonyMgr = Application.Context.GetSystemService(Context.TelephonyService) as TelephonyManager;
string deviceId = telephonyMgr.DeviceId ?? "UNAVAILABLE";
// Compute hash of device ID so we are sure enough bytes have been gathered for the key
byte[] bytes = null;
using (SHA1 sha1 = SHA1.Create())
bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(deviceId));
// Get last 8 bytes from device ID hash as a key
byte[] desKey = new byte[8];
Array.Copy(bytes, bytes.Length - desKey.Length, desKey, 0, desKey.Length);
return desKey;
}
private byte[] Encrypt(byte[] key, byte[] plainData)
{
if (key == null)
throw new ArgumentNullException("key");
if (plainData == null)
throw new ArgumentNullException("plainData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
// desProvider.IV should be automatically filled with random bytes when DESCryptoServiceProvider instance is created
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
using (MemoryStream encryptedStream = new MemoryStream())
{
// Write IV at the beginning of memory stream
encryptedStream.Write(desProvider.IV, 0, desProvider.IV.Length);
// Perform encryption and append encrypted data to the memory stream
using (ICryptoTransform encryptor = desProvider.CreateEncryptor())
{
byte[] encryptedData = encryptor.TransformFinalBlock(plainData, 0, plainData.Length);
encryptedStream.Write(encryptedData, 0, encryptedData.Length);
}
return encryptedStream.ToArray();
}
}
}
private byte[] Decrypt(byte[] key, byte[] encryptedData)
{
if (key == null)
throw new ArgumentNullException("key");
if (encryptedData == null)
throw new ArgumentNullException("encryptedData");
using (DESCryptoServiceProvider desProvider = new DESCryptoServiceProvider())
{
if (!desProvider.ValidKeySize(key.Length * 8))
throw new CryptographicException("Key with invalid size has been specified");
desProvider.Key = key;
if (encryptedData.Length <= desProvider.IV.Length)
throw new CryptographicException("Too short encrypted data has been specified");
// Read IV from the beginning of encrypted data
// Note: New byte array needs to be created because data written to desprovider.IV are ignored
byte[] iv = new byte[desProvider.IV.Length];
Array.Copy(encryptedData, 0, iv, 0, iv.Length);
desProvider.IV = iv;
desProvider.Mode = CipherMode.CBC;
desProvider.Padding = PaddingMode.PKCS7;
// Remove IV from the beginning of encrypted data and perform decryption
using (ICryptoTransform decryptor = desProvider.CreateDecryptor())
return decryptor.TransformFinalBlock(encryptedData, desProvider.IV.Length, encryptedData.Length - desProvider.IV.Length);
}
}
It is really hard to tell what exactly was problem with your code because your decryption method did not work for me at all - most likely because it is using CryptoStream in write mode for decryption which seems a little odd to me.
So much for the code. Now let's get to encryption which is really really weak. It is more just an obfuscation that should protect the data from being accidentally displayed in plain text form (some people use BASE64 encoding for the same thing). The main cause of this is relatively old encryption algorithm and easily predictable encryption key. AFAIK every application running on the same device can read device ID without any privileges. That means any application can decrypt your data. Of course your SQLite database is probably accessible only to your application but that can no longer be true if you remove the SD card or root your phone. To make this a little better you could for example ask user to provide a password and then use it to derive unique encryption key but that is completely different problem. Anyway I am not really sure what you are trying to achieve with this encryption - it may be fully sufficient for your needs even if it can be considered to be weak.
Hope this helps.

Categories

Resources