I have tried using Blowfish (CBC) technique for encrypting / decrypting a text from PHP to Flash.
After hours of investigation and research, I got to know that AS3Crypto could be used for decryption of Blowfish (CBC Mode).
In a simple example, I'm using Mcrypt (A Library for PHP) to encrypt the text:
const CYPHER = 'blowfish';
const MODE = 'cbc';
const KEY = '12345';
public function encrypt($plaintext)
{
$td = mcrypt_module_open(self::CYPHER, '', self::MODE, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, self::KEY, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return $iv.$crypttext;
}
Then, I can transfer the output by encoding it using Base64.
So for example, if we have the original text as (without quotations) "stackoverflow" and the key as "123456", the output will be (base64):
MUXl8mBS9OsvxTbLAiCrAMp851L8vVD0
Till now there is no problem.
Now when I shift this encoded text to flash, I can get it without any problem.
You can try going to http://crypto.hurlant.com/demo/CryptoDemo.swf and then select "Secret Key" tab, and choose encryption as "Blowfish", mode as "CBC", Padding as "none" and tick the "Prepend IV to cipher text" option. After that, you can successfully decrypt the text above, using the key, and get the "stackoverflow" text again.
So, till now I know that its possible to convert from Mcrypt to AS3Crypt, and then I tried to use AS3Crypto library in flash (You can get it from: http://code.google.com/p/as3crypto/).
I made a new actionscript file which has the following content to test whether or not the encryptions would be the same (I couldn't figure out how to decrypt it because of the main problem):
package
{
import com.hurlant.crypto.Crypto;
import com.hurlant.util.Hex;
import com.hurlant.crypto.hash.HMAC;
import com.hurlant.crypto.hash.IHash;
import com.hurlant.crypto.hash.MD5;
import com.hurlant.crypto.hash.SHA1;
import com.hurlant.crypto.hash.SHA224;
import com.hurlant.crypto.hash.SHA256;
import com.hurlant.crypto.prng.ARC4;
import com.hurlant.crypto.symmetric.AESKey;
import com.hurlant.crypto.symmetric.BlowFishKey;
import com.hurlant.crypto.symmetric.CBCMode;
import com.hurlant.crypto.symmetric.CFB8Mode;
import com.hurlant.crypto.symmetric.CFBMode;
import com.hurlant.crypto.symmetric.CTRMode;
import com.hurlant.crypto.symmetric.DESKey;
import com.hurlant.crypto.symmetric.ECBMode;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.symmetric.IMode;
import com.hurlant.crypto.symmetric.IPad;
import com.hurlant.crypto.symmetric.ISymmetricKey;
import com.hurlant.crypto.symmetric.IVMode;
import com.hurlant.crypto.symmetric.NullPad;
import com.hurlant.crypto.symmetric.OFBMode;
import com.hurlant.crypto.symmetric.PKCS5;
import com.hurlant.crypto.symmetric.SimpleIVMode;
import com.hurlant.crypto.symmetric.TripleDESKey;
import com.hurlant.crypto.symmetric.XTeaKey;
import flash.utils.ByteArray;
import com.hurlant.crypto.rsa.RSAKey;
import com.hurlant.util.Base64;
public class BlowFish
{
/**
* Encrypts a string.
* #param text The text string to encrypt.
* #param key A cipher key to encrypt the text with.
*/
/**
* Decrypts an encrypted string.
* #param text The text string to decrypt.
* #param key The key used while originally encrypting the text.
*/
static public function encrypt( s :String, k :String ) :String
{
var key :ByteArray = Hex.toArray(k);
var data :ByteArray = Hex.toArray(Hex.fromString(s));
var pad :IPad = new NullPad();
var cipher :ICipher = Crypto.getCipher("blowfish-cbc", key, pad);
pad.setBlockSize(cipher.getBlockSize());
cipher.encrypt(data);
var result :String = Hex.fromArray(data);
var ivmode :IVMode = cipher as IVMode;
var iv :String = Hex.fromArray(ivmode.IV);
return Base64.encodeByteArray(Hex.toArray(Hex.fromArray(ivmode.IV) + Hex.fromArray(data)));
}
}
}
And I've used the following code to get out the result:
import BlowFish;
var $key:String = "123456";
var $encryption:String = BlowFish.encrypt("stackoverflow", $key);
trace( $encryption );
The problem is that I couldn't match the following outputs together.
I don't have any idea about actionscript, so you will obviously find a lot of mistakes in it.
I will be really appreciated about any explanation and solution with an example to figure out how to successfully decrypt the encrypted text in flash using AS3Crypto.
Thank you.
hope this is helpful:
public static function encryptString(encString : String = "") : String
{
var kdata : ByteArray = Hex.toArray(Hex.fromString(k))
var _method : String = "simple-blowfish-ecb";
var pad : IPad = new NullPad;
var crypt : ICipher = Crypto.getCipher(_method, kdata, pad);
var data : ByteArray = Hex.toArray(Hex.fromString(encString));
pad.setBlockSize(crypt.getBlockSize());
crypt.encrypt(data);
encString = Base64.encodeByteArray(data);
return encString;
}
Related
I need to encrypt user and password with AES-256-CBC and PKCS7 padding to connect to a .NET server application that it is not mine.
Firstly, I got the public server key using Diffie-Hellman algorithm, following the instructions on this site:
https://doc.developer.milestonesys.com/mipsdkmobile/index.html?base=content_0.html&tree=tree_home.html
The code to get the public key from server is:
private const DH_PRIME = 'F488FD584E49DBCD20B49DE49107366B336C380D451D0F7C88B31C7C5B2D8EF6F3C923C043F0A55B188D8EBB558CB85D38D334FD7C175743A31D186CDE33212CB52AFF3CE1B1294018118D7C84A70A72D686C40319C807297ACA950CD9969FABD00A509B0246D3083D66A45D419F9C7CBD894B221926BAABA25EC355E92F78C7';
private const DH_GENERATOR = '02';
$privateKey = gmp_init(uniqid(), 32);
$publicKey = gmp_powm(self::DH_GENERATOR, $privateKey, base_convert( self::DH_PRIME, 16,10));
Once I got the key, so it is working great here, I must compute the server key with my private key, previously decoding base64 server key, and converting it from binary to hexadecimal:
// Decode server public returned key (encoded in base 64)
$serverPKBase64Decoded = base64_decode($apiResponse['response']['Command']['OutputParams']['Param']['7']['#attributes']['Value']);
$serverPublicKey16 = bin2hex($serverPKBase64Decoded);
// Calculate shared key
$prime = gmp_init(self::DH_PRIME,16);
$serverPk = gmp_init($serverPublicKey, 16);
$localPk = gmp_init($localPrivateKey,32);
$sharedKey = gmp_powm($serverPk, $localPk, $prime);
// Get shared key as hexadecimal value
$sharedKeyHex = gmp_strval($sharedKey, 16);
Now, it comes the part to get the initialization vector (iv) and the key to encrypt the username and password:
$iv = substr($sharedKeyHex,0,16);
$key = strlen($sharedKeyHex)-32,32);
// Encrypt username and password
$encryptedUsername = base64_encode(openssl_encrypt($username,$cipher,$key,OPENSSL_RAW_DATA,$iv));
$encryptedPassword = base64_encode(openssl_encrypt($password,$cipher,$key,OPENSSL_RAW_DATA,$iv));
When I send the data and got the response, it throws an error 16 with the text "Incorrect public key".
Our provider's SDK gives examples (on JS with CryptoJS) where the login process is equivalent. Please, could you confirm the code is equal in JS than in PHP:
JS Code:
/**
* Encode a string using client and server public keys
*
* #param str string String to encode
* #access public
* #return string Base64 encoded encrypted string
*/
this.encodeString = function(str) {
var secretString = this._sharedKey || this.getSharedKey().substring(0, 96);
var key = CryptoJS.enc.Hex.parse(secretString.substring(32, 96));
var iv = CryptoJS.enc.Hex.parse( secretString.substring(0,32) );
var params = { 'iv': iv };
if (XPMobileSDKSettings.defaultEncryptionPadding && CryptoJS.pad[XPMobileSDKSettings.defaultEncryptionPadding]) {
params.padding = CryptoJS.pad[XPMobileSDKSettings.defaultEncryptionPadding];
}
return CryptoJS.AES.encrypt(str, key, params).ciphertext.toString(CryptoJS.enc.Base64);
};
I have tested with many PADDINGS but I am not able to get it working. Any ideas why it is not working or what I must change to get it working?
You should prefix your username & password with your own public key, otherwise the server cannot calculate the shared key.
I am creating a JSON file in PHP and I need to transfer it to the kotlin app
The requirements are that the information in the file be locked so that it cannot be modified
I used encryption and decryption in PHP and kotlin using SSL
But I could not combine them
Where should I put PHP keys in kotlin?
These IS the codes I work with:
php
$cipher = "aes-128-ctr";
$iv = base64_decode("bVQzNFNhRkQ1Njc4UUFaWA==");
$plaintext = "Encryption information";
$ciphertext = openssl_encrypt($plaintext, $cipher, $kay, $options=0, $iv);
echo $ciphertext; // 19t56L06a5m934HbeJKoVDxGErTBgg==
Kotlin
import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
object AESEncyption {
const val secretKey = "tK5UTui+DPh8lIlBxya5XVsmeDCoUl6vHhdIESMB6sQ="
const val salt = "QWlGNHNhMTJTQWZ2bGhpV3U="
const val iv = "bVQzNFNhRkQ1Njc4UUFaWA=="
fun decrypt(strToDecrypt: String) : String? {
try {
val ivParameterSpec = IvParameterSpec(Base64.decode(iv, Base64.DEFAULT))
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val spec = PBEKeySpec(secretKey.toCharArray(), Base64.decode(salt, Base64.DEFAULT), 10000, 256)
val tmp = factory.generateSecret(spec);
val secretKey = SecretKeySpec(tmp.encoded, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
return String(cipher.doFinal(Base64.decode(strToDecrypt, Base64.DEFAULT)))
}
catch (e: Exception) {
println("Error while decrypting: $e");
}
return null
}
}
I deleted the unnecessary code
I do not know what is the secretKey and the salt in Kotlin code
And where do I put the $cipher from PHP in the kotlin code
I switched to aes-128-gcm so I would not have to tag
There are several issues within your codes and my answer can just argue on how you present the encryption key (you called it "kay") to the encryption function.
As the encryption key looks like a Base64 encoded string the following steps base on that.
A) I'm decoding the key-string with this command:
$kay = base64_decode("tK5UTui+DPh8lIlBxya5XVsmeDCoUl6vHhdIESMB6sQ=");
and receive a key length of 32 bytes - that is important for the next step.
B) As your AES algorithm is set to AES-CTR-128 OpenSSL will strip of all bytes longer than 16 bytes. That said, how does your key looks like in hex encoding:
echo 'original key: ' . bin2hex(($kay)) . PHP_EOL;
original key: b4ae544ee8be0cf87c948941c726b95d5b267830a8525eaf1e1748112301eac4
used key: b4ae544ee8be0cf87c948941c726b95d
used key Base64 tK5UTui+DPh8lIlBxya5XQ==
C) you can test that yourself with both keys - both will result in the ciphertext (Base64 encoded) of
OM3XUymvpRm0bJdEVC+TiRc/VKy/lA==
and not, as you said in your question, 19t56L06a5m934HbeJKoVDxGErTBgg==.
D) now put all data together and switch to Kotlin (I'm using Java, but that should be no problem for you):
The simple program will return the original plaintext:
original plaintext: Encryption information
Security warning: The following code uses static secret keys and IV's, has no exception handling and is for educational purpose only:
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class SoAnswer {
public static void main(String[] args) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String ciphertextBase64 = "OM3XUymvpRm0bJdEVC+TiRc/VKy/lA==";
byte[] ciphertext = Base64.getDecoder().decode(ciphertextBase64);
byte[] key = Base64.getDecoder().decode("tK5UTui+DPh8lIlBxya5XQ==");
byte[] iv = Base64.getDecoder().decode("bVQzNFNhRkQ1Njc4UUFaWA==");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decryptedtext = cipher.doFinal(ciphertext);
System.out.println("original plaintext: " + new String(decryptedtext));
}
}
PHP version: 5.6.39
Node.js version: 10.9.0
Objective: Encrypt using PHP and decrypt using Node.js
Stuck Point: I'm currently assuming that both the PHP and Node.js bindings to OpenSSL make use of PKCS7 padding. Do PHP and Node.js use incompatible bindings to OpenSSL?
Example PHP encryption/decryption code:
class SymmetricEncryption {
public static function simpleEncrypt($key, $plaintext) {
$iv = openssl_random_pseudo_bytes(16);
$ciphertext = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv)
return base64_encode($iv) . "." . base64_encode($ciphertext);
}
public static function simpleDecrypt($key, $token) {
list($iv, $ciphertext) = explode(".", $token);
return openssl_decrypt(
base64_decode($ciphertext),
"aes-256-cbc",
$key,
OPENSSL_RAW_DATA,
base64_decode($iv)
);
}
}
Example Node.js encryption/decryption code:
class SymmetricEncryption {
static simpleEncrypt(key: Buffer, plaintext: string): string {
const iv = randomBytes(16)
const cipher = createCipheriv('aes-256-cbc', key, iv)
const encrypted = cipher.update(plaintext)
const token = Buffer.concat([encrypted, cipher.final()])
return iv.toString('base64') + "." + token.toString('base64')
}
static simpleDecrypt(key: Buffer, token: string): string {
const [iv, ciphertext] = token.split(".").map(piece => Buffer.from(piece, 'base64'))
const decipher = createDecipheriv('aes-256-cbc', key, iv)
const output = decipher.update(ciphertext)
return Buffer.concat([output, decipher.final()]).toString('utf8')
}
}
I've tested each implementation independently of one another successfully, but when encrypting in PHP and decrypting in node.js I get the following error:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
The stack trace points me to the offending line, which is decipher.final() in the simpleDecrypt method.
I'm using the following (failing) unit test to verify my implementation
it('should be able to decrypt values from php', () => {
const testAesKey = Buffer.from('9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8', 'hex')
const phpToken = 'oMhL/oIPAGQdMvphMyWdJw==.bELyRSIwy+nQGIyLj+aN8A=='
const decrypted = SymmetricEncryption.simpleDecrypt(testAesKey, phpToken)
expect(decrypted).toBe('hello world')
})
The phpToken variable I'm using here was created using the following code:
$testAesKey = "9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8";
echo SymmetricEncryption::simpleEncrypt($testAesKey, "hello world");
I suspect your issue is caused by how you pass your key in PHP. The documentation for openssl_encrypt doesn't specify anywhere that it interprets the key as hex. It does, however, truncate keys that are too long.
What is likely happening here is PHP is truncating your 64 character hex string down to 32 bytes, and using those for the key. Try using hex2bin on your PHP key before using it - this should fix your issue!
$testAesKey = hex2bin("9E9CEB8356ED0212C37B4D8CEA7C04B6239175420203AF7A345527AF9ADB0EB8");
I have a web-service in php that generates a keypair to encrypt a message, and one application in java that retrives the privatekey and decrypt the message.
For php I'm using http://phpseclib.sourceforge.net/ and have this two files:
keypair.php
<?php
set_time_limit(0);
if( file_exists('private.key') )
{
echo file_get_contents('private.key');
}
else
{
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->createKey();
$res = $rsa->createKey();
$privateKey = $res['privatekey'];
$publicKey = $res['publickey'];
file_put_contents('public.key', $publicKey);
file_put_contents('private.key', $privateKey);
}
?>
encrypt.php
<?php
include('Crypt/RSA.php');
//header("Content-type: text/plain");
set_time_limit(0);
$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->loadKey(file_get_contents('public.key')); // public key
$plaintext = 'Hello World!';
$ciphertext = $rsa->encrypt($plaintext);
echo base64_encode($ciphertext);
?>
and in java I have this code:
package com.example.app;
import java.io.DataInputStream;
import java.net.URL;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
public class MainClass {
/**
* #param args
*/
public static void main(String[] args)
{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
try {
BASE64Decoder decoder = new BASE64Decoder();
String b64PrivateKey = getContents("http://localhost/api/keypair.php").trim();
String b64EncryptedStr = getContents("http://localhost/api/encrypt.php").trim();
System.out.println("PrivateKey (b64): " + b64PrivateKey);
System.out.println(" Encrypted (b64): " + b64EncryptedStr);
SecretKeySpec privateKey = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");
Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainText = decoder.decodeBuffer(b64EncryptedStr);
System.out.println(" Message: " + plainText);
}
catch( Exception e )
{
System.out.println(" Error: " + e.getMessage());
}
}
public static String getContents(String url)
{
try {
String result = "";
String line;
URL u = new URL(url);
DataInputStream theHTML = new DataInputStream(u.openStream());
while ((line = theHTML.readLine()) != null)
result = result + "\n" + line;
return result;
}
catch(Exception e){}
return "";
}
}
My questions are:
Why I'm having a exception saying "not an RSA key!"?
How can I improve this code? I have used base64 to avoid encoding and comunication errors between Java and PHP.
This concept is correct? I mean, I'm using it correctly?
With the help of above answer, I got that SecretKeySpec is used wrong, and I found that PEM file from OpenSSL isn't a 'standart format', so I need to use the PEMReader to convert it to PrivateKey class.
Here is my working class:
package com.example.app;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.StringReader;
import java.net.URL;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import javax.crypto.Cipher;
import org.bouncycastle.openssl.PEMReader;
import sun.misc.BASE64Decoder;
public class MainClass {
/**
* #param args
*/
public static void main(String[] args)
{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
try {
BASE64Decoder decoder = new BASE64Decoder();
String b64PrivateKey = getContents("http://localhost/api/keypair.php").trim();
String b64EncryptedStr = getContents("http://localhost/api/encrypt.php").trim();
System.out.println("PrivateKey (b64): " + b64PrivateKey);
System.out.println(" Encrypted (b64): " + b64EncryptedStr);
byte[] decodedKey = decoder.decodeBuffer(b64PrivateKey);
byte[] decodedStr = decoder.decodeBuffer(b64EncryptedStr);
PrivateKey privateKey = strToPrivateKey(new String(decodedKey));
Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainText = cipher.doFinal(decodedStr);
System.out.println(" Message: " + new String(plainText));
}
catch( Exception e )
{
System.out.println(" Error: " + e.getMessage());
}
}
public static String getContents(String url)
{
try {
String result = "";
String line;
URL u = new URL(url);
DataInputStream theHTML = new DataInputStream(u.openStream());
while ((line = theHTML.readLine()) != null)
result = result + "\n" + line;
return result;
}
catch(Exception e){}
return "";
}
public static PrivateKey strToPrivateKey(String s)
{
try {
BufferedReader br = new BufferedReader( new StringReader(s) );
PEMReader pr = new PEMReader(br);
KeyPair kp = (KeyPair)pr.readObject();
pr.close();
return kp.getPrivate();
}
catch( Exception e )
{
}
return null;
}
}
Here is my keypair.php
<?php
set_time_limit(0);
if( file_exists('private.key') )
{
echo base64_encode(file_get_contents('private.key'));
}
else
{
include('Crypt/RSA.php');
$rsa = new Crypt_RSA();
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$res = $rsa->createKey(1024);
$privateKey = $res['privatekey'];
$publicKey = $res['publickey'];
file_put_contents('public.key', $publicKey);
file_put_contents('private.key', $privateKey);
echo base64_encode($privateKey);
}
?>
and my encrypt.php
<?php
include('Crypt/RSA.php');
set_time_limit(0);
$rsa = new Crypt_RSA();
$rsa->setHash('sha1');
$rsa->setMGFHash('sha1');
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_OAEP);
$rsa->loadKey(file_get_contents('public.key')); // public key
$plaintext = 'Hello World!';
$ciphertext = $rsa->encrypt($plaintext);
$md5 = md5($ciphertext);
file_put_contents('md5.txt', $md5);
file_put_contents('encrypted.txt', base64_encode($ciphertext));
echo base64_encode($ciphertext);
?>
I hope it helps anyone and thanks.
A few thoughts.
Shouldn't you be echo'ing out $privatekey in the else as well?
Are you using the latest Git version of phpseclib? I ask because a while ago there was this commit:
https://github.com/phpseclib/phpseclib/commit/e4ccaef7bf74833891386232946d2168a9e2fce2#phpseclib/Crypt/RSA.php
The commit was inspired by https://stackoverflow.com/a/13908986/569976
Might be worthwhile if you change your tags up a bit to include bouncycastle and phpseclib. I'd add those tags but some tags will have to be removed since you're already at the limit of 5. I'll let you decide which ones to remove (if you even want to do that).
SecretKeySpec privateKey = new SecretKeySpec( decoder.decodeBuffer(b64PrivateKey) , "AES");
b64PrivateKey is supposed to contain the private key right? 'cause looking it up in the docs it looks like SecretKeySpec is only intended for symmetric algorithms (like AES) - not asymmetric ones like RSA.
I did some digging into the classes you're using and it seems like what you've posted has most of the default parameters matching your explicit parameters. However, this doesn't ensure that your configuration has those all set to match the current documentation if you're using older implementations.
Also, a tip from a senior Facebook security engineer who discussed a similar issue in a lecture recently; different libraries implementing the same security protocols will oftentimes be incompatible and even the same libraries in different environments or languages will oftentimes fail to work together. With that in mind, a few things to try given that working examples similar to your setup exist online:
Make sure you're using the latest versions of libraries. Also note that some javax functions and classes are deprecated and suggest using java.security now (didn't check if this applies to your case).
Force the key size to be consistent (1024 or 2048). The java implementation can do either and I didn't find consistent documentation for both libraries saying what your configuration default would be used (could be causing problem #2, though you might get a different exception for invalid key size).
Ensure that your privatekey matches expectation (length/reads the same between java and php).
Force default values to be explicitly defined (set CryptRSA hash to sha1, key lengths, anything else you can explicitly set).
Try encrypting the same message with both java and php to see if you can get the same outputs. Do this after ensuring your keys read the same and don't throw exceptions while being used in both applications. Encrypting a single character can tell you if the same padding scheme is actually being used (it appears from the source code that both use MGF1, but it never hurts to check outputs).
Finally try taking an example for php to java encryption which is said to already work and do one change at a time until you get back to your current encryption scheme. I saw a few examples googling quickly which used different parameters and settings with CryptRSA and java security that stated they were working together. Take a working example and try swapping the hash function and then the encryption, etc.
I'm kind of new to ActionScript 3 at the moment and been trying to use the as3crypto library to encrypt some data with the blowfish algorithm before submitting it to the server for processing. I know you can use https, but most browsers still display the outbound data making it very easy for a user to fake a request. That's why I want to let the user see the page request, but not be able to read the data without decrypting.
Unfortunately for me the deocumentation on the as3crypto library is pretty much non existent aside from comments in the code (which don't help too much). I've set up the flash side of things with a couple static functions to "implement" the as3crypto blowfish encryption and they work fine for encrypting/decrypting within flash only. The problem comes when I try to use the key to decrypt in PHP using the mcrypt library. The output I get is not the original code and I've spent a couple days trying to figure out why to no avail.
Below is the code and explanations. For purposes of this example, the key used was 'mykey' (without the quotes) and the encoded data was 'Hello World' (again without the quotes).
Flash code (as3crypto blowfish helper):
package lib.ef.crypto
{
import com.hurlant.util.Base64;
import com.hurlant.crypto.Crypto;
import flash.utils.ByteArray;
import com.hurlant.crypto.symmetric.IPad;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.symmetric.NullPad;
public class Blowfish
{
/**
* Encrypts a string.
* #param text The text string to encrypt.
* #param key A cipher key to encrypt the text with.
*/
static public function encrypt($text:String, $key:String=""):String
{
var cryptKey:ByteArray = new ByteArray();
cryptKey.writeUTF( $key );
var iPad:IPad = new NullPad();
var crypt:ICipher = Crypto.getCipher('blowfish-cfb',cryptKey,iPad);
iPad.setBlockSize( crypt.getBlockSize() );
var cryptText:ByteArray = new ByteArray();
cryptText.writeUTF( $text );
crypt.encrypt( cryptText );
trace( Base64.encodeByteArray( cryptText ) );
return null;
}
static public function decrypt($text:String, $key:String=""):String
{
return new String();
}
}
}
The output of that varies from run to run, but for the purpose of this example run, the base64 encoded output I get is 'EkKo9htSJUnzBmxc0A=='
When I bring that code into PHP it is base64 decoded before being passed into the method below to decrypt it:
public static function decrypt($crypttext,$key)
{
if( !function_exists('mcrypt_module_open') ) trigger_error('The blowfish encryption class requires the Mcrypt library to be compiled into PHP.');
$plaintext = '';
$td = mcrypt_module_open('blowfish', '', 'cfb', '');
$blocksize = mcrypt_enc_get_block_size($td);
$iv = substr($crypttext, 0, $blocksize);
$crypttext = substr($crypttext, $blocksize);
if (true)
{
mcrypt_generic_init($td, $key, $iv);
$plaintext = mdecrypt_generic($td, $crypttext);
}
return $plaintext;
}
At this point the output is completely unreadable. I suspect the issue may be related to the fact that either the as3crypto implementation of blowfish isn't correct (unlikely) or it could be something to do with the padding it uses (currently null padding) or lastly I've thought it may have something to do with the randomly generated initialization vector in as3crypto not being prepended to the front of the encoded string? That last one I haven't been able to really test because the as3crypto library is large, complicated, and not documented much at all. I've googled this issue and tested everything for a couple days now and I just keep coming up with unusable data in PHP. I know if I can get the Flash to PHP system working, I can reverse engineer it to get the PHP to Flash encryption running as well.
I welcome all input on this matter as it's actually been costing me sleep at night lol Thank you in advance :)
I've done some further testing today and attempted to see if it was the initialization vector as I suspected. I don't believe that's the problem. I modified some things in flash so that I could get an output of the IV used to generate the encoded output:
package lib.ef.crypto
{
import com.hurlant.util.Base64;
import com.hurlant.crypto.Crypto;
import flash.utils.ByteArray;
import com.hurlant.crypto.symmetric.IPad;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.symmetric.NullPad;
public class Blowfish
{
/**
* Encrypts a string.
* #param text The text string to encrypt.
* #param key A cipher key to encrypt the text with.
*/
static public function encrypt($text:String, $key:String=""):String
{
var cryptKey:ByteArray = new ByteArray();
cryptKey.writeUTF( $key );
var iPad:IPad = new NullPad();
var crypt = Crypto.getCipher('blowfish-cfb',cryptKey,iPad);
iPad.setBlockSize( crypt.getBlockSize() );
var cryptText:ByteArray = new ByteArray();
cryptText.writeUTF( $text );
crypt.encrypt( cryptText );
cryptText.position = 0;
var iv:ByteArray = crypt.IV;
iv.position = 0;
trace( Base64.encodeByteArray( iv ) );
trace( Base64.encodeByteArray( cryptText ) );
return null;
}
static public function decrypt($text:String, $key:String=""):String
{
return new String();
}
}
}
For this example I got an encoded IV of '1bcGpqIbWRc=' and encoded encrypted data of 'XpgART3hNQO10vcgLA==' I plugged those into a modified PHP function after base64_decode() ing them:
public static function decrypt($crypttext,$key,$iv=NULL)
{
if( !function_exists('mcrypt_module_open') ) trigger_error('The blowfish encryption class requires the Mcrypt library to be compiled into PHP.');
$plaintext = '';
$td = mcrypt_module_open('blowfish', '', 'cfb', '');
if( $iv === NULL ){
$ivsize = mcrypt_enc_get_iv_size($td);
echo '<pre>'.$ivsize.'</pre>';
$iv = substr($crypttext, 0, $ivsize);
echo '<pre>'.strlen($iv).'</pre>';
$crypttext = substr($crypttext, $ivsize);
}
if ($iv)
{
mcrypt_generic_init($td, $key, $iv);
$plaintext = mdecrypt_generic($td, $crypttext);
}
return $plaintext;
}
Even this output is incorrect. I've done some tests to make sure the IV is the correct size both in flash and PHP, but for some reason the PHP side of things just can't decrypt the blowfish encoded output from Flash. I've tried using both NULL and PKCS5 padding in as3crypto and neither works with PHP's system. I've tested to make sure the IV strings are the same in both Flash and PHP. They're both using the same keys. Both are using CFB mode. I don't get it. Same algorithm, same key, same IV, same mode, but they can't decrypt from each other. It's seeming to me that the as3crypto implementation of blowfish may be incorrect. Can anyone confirm this?
After doing some digging through the as3Crypto library files and demo code, I found that the problem is I need to use the getCipher function with simple-blowfish-cfb mode instead of blowfish-cfb mode. The encrypted output from calling crypt.encyrpt( cryptText ) will then be already prefixed with the IV of the algorithm so you make a single call to Base64.encodeByteArray( cryptText ) to get the output to send to PHP. When you initialize PHP the way I have above, it will slice off the IV from the string and decrypt properly. Hopefully this helps anyone else who comes along with this problem.
The "correct" flash and PHP code* is below for all you TLDR;ers who just want a quick copy/paste solution :P
*Note: I had to remove some of my application specific calls in both code samples and did not test them to insure they're 100% functional, but they should illustrate the concept/structure enough that if they don't work correctly "out of the box" you can easily mend them for your use.
PHP "helper" class:
class Blowfish
{
public static function encrypt($plaintext,$key)
{
if( !function_exists('mcrypt_module_open') ) trigger_error('The blowfish encryption class requires the Mcrypt library to be compiled into PHP.');
$td = mcrypt_module_open('blowfish', '', 'cbc', '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
$out = $iv.$crypttext;
return $out;
}
public static function decrypt($crypttext,$key)
{
if( !function_exists('mcrypt_module_open') ) trigger_error('The blowfish encryption class requires the Mcrypt library to be compiled into PHP.');
$plaintext = '';
$td = mcrypt_module_open('blowfish', '', 'cbc', '');
$ivsize = mcrypt_enc_get_iv_size($td);
$iv = substr($crypttext, 0, $ivsize);
$crypttext = substr($crypttext, $ivsize);
if ($iv)
{
mcrypt_generic_init($td, $key, $iv);
$plaintext = mdecrypt_generic($td, $crypttext);
}
return $plaintext;
}
}
Flash "helper" class:
package [your package name]
{
import com.hurlant.util.Base64;
import com.hurlant.util.Hex;
import com.hurlant.crypto.Crypto;
import flash.utils.ByteArray;
import com.hurlant.crypto.symmetric.IPad;
import com.hurlant.crypto.symmetric.ICipher;
import com.hurlant.crypto.symmetric.IVMode;
import com.hurlant.crypto.symmetric.NullPad;
public class Blowfish
{
/**
* Encrypts a string.
* #param txt The text string to encrypt.
* #param k A cipher key to encrypt the text with.
*/
static public function encrypt(txt:String, k:String=""):String
{
var kdata:ByteArray;
kdata = Hex.toArray(Hex.fromString(k));
var data:ByteArray;
data = Hex.toArray(Hex.fromString(txt));
var pad:IPad = new NullPad;
var mode:ICipher = Crypto.getCipher('simple-blowfish-cbc', kdata, pad);
pad.setBlockSize(mode.getBlockSize());
mode.encrypt(data);
return Base64.encodeByteArray( data );
}
/**
* Decrypts a string.
* #param txt The text string to decrypt.
* #param k A cipher key to decrypt the text with.
*/
static public function decrypt(txt:String, k:String=""):String
{
var kdata:ByteArray;
kdata = Hex.toArray(Hex.fromString( Base64.decode( k ) ));
var data:ByteArray;
data = Hex.toArray(Hex.fromString(txt));
var pad:IPad = new NullPad;
var mode:ICipher = Crypto.getCipher('simple-blowfish-cbc', kdata, pad);
pad.setBlockSize(mode.getBlockSize());
mode.decrypt(data);
data.position = 0;
return data.readUTFBytes( data.bytesAvailable );
}
}
}
Thanx!
See here the correct "decrypt":
static public function decrypt(txt:String, k:String=""):String{
var kdata:ByteArray;
kdata = Hex.toArray(Hex.fromString(k));
var data:ByteArray;
data = Base64.decodeToByteArray(txt);
var pad:IPad = new NullPad;
var mode:ICipher = Crypto.getCipher('simple-blowfish-cbc', kdata, pad);
pad.setBlockSize(mode.getBlockSize());
mode.decrypt(data);
data.position = 0;
return data.readUTFBytes( data.bytesAvailable );
}