AES256 from iOS to PHP - php

I'm trying to decrypt it in Xcode with AES-Crypt-Objc. I'have tried really everything..different libariers and so on..
..anyhting goes wrong, but I don't know what pls. help...
UPDATE (another opinion)
Now i try this:
function mc_encrypt($encrypt, $key = "12345678901234567890123456789012")
{
$encrypt = "Affe";
$iv2 = '';
for($i=0;$i<16;$i++){ $iv2 .= "\0"; }
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, ($encrypt), MCRYPT_MODE_CBC,$iv2);
$encode= base64_encode($ciphertext);
return $encode;
}
in Obj-C I use also the same framework (still IV is now nil)
...still nothing works...
AND some Objc-C Code:
note: request responseString is the string comes from above.
NSString *key = #"12345678901234561234567890123456";
NSLog(#"decrypted: %#",[AESCrypt decrypt:[request responseString] password:key]);
output is sometimes nothing, sometimes null.

For most systems, encryption should be non-deterministic — encrypting the same plaintext twice should almost never give the same ciphertext. Why? Let's say you send "attack at dawn" at midnight on day 0 to your accomplice, the attack fails (but you get away unharmed), and at midnight on day 1 you send "attack at dawn" again...
Apart from that, let me count the ways:
You haven't shown any ObjC code, so it's difficult to tell what's going wrong there.
Your PHP code uses, oddly, a 26-character "key". It should be 16 bytes for AES-128. I don't know how PHP's mcrypt handles overlong keys; the ObjC code adds zero-padding in FixKeyLengths().
Both the PHP (as you've shown it) and ObjC code (by default) use fixed IVs. Very bad.
They use different IVs, which is probably why you get different answers.
FWIW, I've seen that that (or similar) ObjC code all over the place. I can't un-recommend it enough; it really isn't suitable for general usage.
Just Use HTTPS.

Related

Nodejs how to implement OpenSSL AES-CBC encryption (from PHP)?

I am currently working on translating an encryption algorithm from PHP to Typescript, to use in a very specific API that requires the posted data to be encrypted with the API key and Secret. Here is the provided example of how to correctly encrypt data in PHP for use with the API (the way of implementing the key and IV can't be changed):
$iv = substr(hash("SHA256", $this->ApiKey, true), 0, 16);
$key = md5($this->ApiSecret);
$output = openssl_encrypt($Data, "AES-256-CBC", $key, OPENSSL_RAW_DATA, $iv);
$completedEncryption = $this->base64Url_Encode($output);
return $completedEncryption;
In the above code, the only thing the base64Url_Encode function does is convert the binary data to a valid Base64URL string.
And now the code as I have implemented it inside Typescript:
import { createHash, createCipheriv } from 'node:crypto'
const secretIV = createHash('sha256').update(this.ApiKey).digest().subarray(0, 16)
// Generate key
/*
Because the OpenSSL function in PHP automatically pads the string with /null chars,
do the same inside NodeJS, so that CreateCipherIV can accept it as a 32-byte key,
instead of a 16-byte one.
*/
const md5 = createHash('md5').update(this.ApiSecret).digest()
const key = Buffer.alloc(32)
key.set(md5, 0)
// Create Cipher
const cipher = createCipheriv('aes-256-cbc', key, secretIV)
let encrypted = cipher.update(data, 'utf8', 'binary');
encrypted += cipher.final('binary');
// Return base64URL string
return Buffer.from(encrypted).toString('base64url');
The above Typescript code only does NOT give the same output as the PHP code given earlier. I have looked into the original OpenSSL code, made sure that the padding algorithms are matching (pcks5 and pcks7) and checked if every input Buffer had the same byte length as the input inside PHP. I am currently thinking if it is some kind of binary malform that is causing the data to change inside Javascript?
I hope some expert can help me out with this question. Maybe I have overlooked something. Thanks in advance.
The stupidity is in the md5 function in PHP, which defaults to hexadecimal output instead of binary output:
md5(string $string, bool $binary = false): string
This is also why the code doesn't complain about the key (constructed from the MD5 hash) is being too small, it is fed 32 bytes after ASCII or UTF8 encoding, instead of the 16 bytes you'd use for AES-128.
Apparently it is using lowercase encoding, although not even that has been specified. You can indicate the encoding for NodeJS as well, see the documentation of the digest method. It also seems to be using lowercase, although I cannot directly find the exact specification of the encoding either.
Once you have completed your assignment, please try and remove the code ASAP, as you should never calculate the IV from the key; they key and IV combination should be unique, so the above code is not IND-CPA secure if the key is reused.
In case you are wondering why it is so stupid: the output of MD5 has been specified in standards, and is binary. Furthermore, it is impossible from the function to see what it is doing, you have to lookup the code. It will also work very badly if you're doing a compare; even if you are comparing strings then it is easy to use upper instead of lowercase (and both are equally valid, uppercase hex is actually easier to read for humans as we focus on the top part of letters more for some reason or other).
Basically it takes the principle of least surprise and tosses it out of the window. The encoding of the output could be made optimal instead, the NodeJS implementation does this correctly.

Adding an encryption method?

Encryption is a topic that I have literally no experience with whatsoever until I actually learn about it at University next year (excluding basic knowledge of simple cyphers). I'll hopefully understand them better in the future, but for now I'd just llike to implement them into my apps without knowing the inner details.
Scenario
I'm trying to secure one of my PHP/NodeJS apps by adding encryption to it. I'm doing this so I can share data between NodeJS and PHP in a secure manner.
PHP server -> Needs to be able to encrypt and decrypt data
NodeJS server -> Only needs to be able to decrypt data
Possible solutions
I've spent hours searching for a decent solution to my problem. I've tried many code samples, but this seems to be the best code sample that I've come across and tested so far.
As you can see from the code, decryption methods have been built for both NodeJs and PHP. Both appear to work very well so far from what I've tested. That's half of my problem solved already. :-)
What I'm trying to do now, is figure our how to implement an encryption function with PHP code. I've tried this:
print "Encrypted: " . base64_encode(openssl_encrypt("Hello world", 'aes-256-cbc', $password));
but I get the output of: aUZaNnFlWWV5M0dObTE1U3pxMENwdz09 when I should in fact be getting the value of edata as output: U2FsdGVkX18M7K+pELP06c4d5gz7kLM1CcqJBbubW/Q=
Does anyone know why this output is different? I'm obviously doing something wrong, but I just can't figure out what I should be doing as there is so much going on in that PHP code... If someone could give me a little guidance that would be great. Thanks!
but I get the output of: aUZaNnFlWWV5M0dObTE1U3pxMENwdz09 when I should in fact be getting the value of edata as output: U2FsdGVkX18M7K+pELP06c4d5gz7kLM1CcqJBbubW/Q=
There are two things to keep in mind:
Unless you specify OPENSSL_RAW_DATA, openssl_encrypt() will automatically base64 encode the output for you, so the second output is double-encoded.
The Node.js snippet you linked above does some funky stuff to generate a key and IV, and your PHP snippet doesn't do this at all.
var rounds = 3;
var data00 = password + salt;
console.log("Data00 (Base64): " + new Buffer(data00, "binary").toString("base64"));
md5_hash = new Array();
md5_hash[0] = crypto.createHash("md5").update(data00).digest("binary");
var result = md5_hash[0];
console.log("MD5-Hash[0] (Base64): " + new Buffer(result, "binary").toString("base64"));
for (i = 1; i < rounds; i++) {
md5_hash[i] = crypto.createHash("md5").update(md5_hash[i - 1] + data00).digest("binary");
result += md5_hash[i];
console.log("Result (Base64): " + new Buffer(result, "binary").toString("base64"));
}
Conversely, your PHP snippet uses a naked $password variable and doesn't pass an IV at all, which generates an error:
php > $password = 'password';
php > echo base64_encode(openssl_encrypt("Hello world", 'aes-256-cbc', $password));
PHP Warning: openssl_encrypt(): Using an empty Initialization Vector (iv) is potentially insecure and not recommended in php shell code on line 1
NlpwenNFNCtJYjhjOWNIYkNHazZCZz09
So those are your immediate sources of failure:
Your key is different.
The Node.js snippet uses an IV, you do not. You need an IV for AES-CBC.
But also: AES-CBC is not secure by itself. See this answer for what to do instead.

Decrypting the .ASPXAUTH Cookie WITH protection=validation

For quite sometime I've been trying to decipher the ASP .ASPXAUTH cookie and decrypt it using PHP. My reasons are huge and I need to do this, there is no alternative. In PHP so far I have successfully managed to read the data from this cookie, but I cannot seem to do it while it is encrypted. Anyway, here it goes...
First you need to alter your servers Web.config file (protection needs to be set to Validation):
<authentication mode="None">
<forms name=".ASPXAUTH" protection="Validation" cookieless="UseCookies" timeout="10080" enableCrossAppRedirects="true"/>
</authentication>
Then in a PHP script on the same domain, you can do the following to read the data, this is a very basic example, but is proof:
$authCookie = $_COOKIE['_ASPXAUTH'];
echo 'ASPXAUTH: '.$authCookie.'<br />'."\n";//This outputs your plaintext hex cookie
$packed = pack("H*",$authCookie);
$packed_exp = explode("\0",$packed);//This will separate your data using NULL
$random_bytes = array_shift($packed_exp);//This will shift off the random bytes
echo print_r($packed_exp,TRUE); //This will return your cookies data without the random bytes
This breaks down the cookie, or at least the unencrypted data:
Now that I know I can get the data, I removed the 'protection="validation"' string from my Web.config and I tried to decrypt it using PHP mcrypt. I have tried countless methods, but here is a promising example (which fails)...
define('ASP_DECRYPT_KEY','0BC95D748C57F6162519C165E0C5DEB69EA1145676F453AB93DA9645B067DFB8');//This is a decryption key found in my Machine.config file (please note this is forged for example)
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, ASP_DECRYPT_KEY, $authCookie, MCRYPT_MODE_CBC, $iv);//$authCookie is the pack()'d cookie data
This however fails. I've tried variations of IV with all zeros # 16 bytes. I've tried different Rijndael sizes (128 vs 256). I've tried base64_decode()ing, nothing seems to work. I've found this stackoverflow post here and started using variations of the key/iv that are made using sha256, but that isn't really working either.
Anybody have a clue what I should do?
I don't know how encryption is made in .NET AuthCookies, but I can try to answer.
Assuming the encryption occurs in AES CBC-IV mode, with randomly generated IVs, you need to first find out where the IV is.
The code snippet you show cannot work, as you are generating a random IV (which will be incorrect). That being said, even if you get the IV wrong, in CBC mode you will only have the first 16 bytes of your decrypted ciphertext "garbled" and the rest will decrypt properly - you can use this as a test to know if you're doing the rest correctly. In practice when using random IVs, it's very likely that it's prepended to the ciphertext. To check if this correct, you can try to check if len(ciphertext) = len(plaintext) + 16. This would mean that most likely the first 16 bytes are your IV (and therefore it should be removed from the ciphertext before attempting to decrypt it).
Also on your code snippet, it seems you are using the key as an ascii-string, whereas it should be a byte array. Try:
define('ASP_DECRYPT_KEY',hex2bin('0BC95D748C57F6162519C165E0C5DEB69EA1145676F453AB93DA9645B067DFB8'));
Also, this seems to be a 32 byte key, so you need to use AES-256. I don't know how the authcookie looks like, but if it's base64 encoded, you also need to decode it first obviously.
Hope this helps!
Note: I don't recomment doing this for important production code, however - because there are many things that can go wrong if you try to implement even your own decryption routine as you are doing here. In particular, I would guess there should be a MAC tag somewhere that you have to check before attempting decryption, but there are many other things that can go wrong implementing your own crypto.
I understand this may not have been possible for the OP but for other people heading down this route here is a simple alternative.
Create a .net web service with a method like:
public FormsAuthenticationTicket DecryptFormsAuthCookie(string ticket)
{
return FormsAuthentication.Decrypt(ticket);
}
Pass cookie to web service from PHP:
$authCookie = $_COOKIE['.ASPXAUTH'];
$soapClient = new SoapClient("http://localhost/Service1.svc?wsdl");
$params= array(
"ticket" => $authCookie
);
$result = $soapClient->DecryptFormsAuthCookie($params);
I know what a pain is to decrypt in PHP something encrypted in .NET and vice versa.
I had to end up coding myself the Rijndael algorithm ( translated it from another language ).
Here is the link to the source code of the algorithm: http://pastebin.com/EnCJBLSY
At the end of the source code there is some usage example.
But on .NET, you should use zero padding when encrypting. Also test it with ECB mode, I'm not sure if CBC works.
Good luck and hope it helps
edit: the algorithm returns the hexadecimal string when encrypts, and also expects hexadecimal string when decrypting.

AES encryption in php and then decryption with Javascript (cryptojs)

I'm searching for a way to make a 2 way encryption of a simple text (5 to 6 numbers and/or characters). The catch is that i want to make the encryption in php and then decrypt it via Javascript. For php i've tested using mcrypt_encode and have gotten it to work, hence when i try to decrypt it with javascript (i'm using the Crypto-js library - http://code.google.com/p/crypto-js/ ) i get no results. Here is the php code i'm using:
$key = "oijhd981727783hy18274";
$text = "1233";
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
$crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC,$iv);
echo base64_encode($crypttext);
and then here is the Javascript code i'm using:
var encrypted = CryptoJS.enc.Base64.parse("LiJU5oYHXRSNsrjMtCr5o2ev7yDFGZId85gh9MEXPeg=");
var key = 'oijhd981727783hy18274';
var decrypted = CryptoJS.AES.decrypt(encrypted, key);
document.write( decrypted.toString(CryptoJS.enc.Utf8) );
As i'm just testing, i copy/paste the output from the php straight into the JS and see if it would return any results, however that doesnt happen. As i'm new to the encryption/decryption part i might be missing something. Any suggestions will be greatly appreciated.
On a side note, as i read a lot of suggestions here about using other types of communication to transfer the data, that would not be possible in this case, as i need to pass this string to a third party software, which will bring it over on a secure area, where i have access to edit only the javascript, this is why i'm trying to encrypt the text in php and place it inside the website's source, from where the third party software will read it as it is encrypted and will transfer it to the secure section, where i will need to decrypt it back via Javascript (i dont have access to php there).
So, after some more digging i came to the following online encryptor/decryptor which led me to the gibberish-aes at GitHub repository.
Inside one of the comments on the first link i found that this JS library has a php equivalent, which seems to be working reasonably well and is fairly easy to deploy:
https://github.com/ivantcholakov/gibberish-aes-php
So thanks to Lars for the answer he provided, i would encourage him to open the repository, i'm sure he'll make someone's life a little bit easier :)
From the CryptoJS documentation:
For the key, when you pass a string, it's treated as a passphrase and used to derive an actual key and IV. Or you can pass a WordArray that represents the actual key. If you pass the actual key, you must also pass the actual IV.
So in your line
var decrypted = CryptoJS.AES.decrypt(encrypted, key);
"oijhd981727783hy18274" is treated as a passphrase to create a key and not as the actual key.
Passing an IV works like this:
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
Some time ago I had the same problem. I finally got to use SlowAES from http://code.google.com/p/slowaes/ with some fixes and ported it to PHP.
Note: The official sources are broken, just as the official PHP port.
Let me know if you're interested. Then I'd open a new repository at GitHub where you can grab everything you need...

Which Blowfish algorithm is the most 'correct'?

I'm running Windows Server 2k8 (maybe that's half the problem?) Anyway, I'm getting different values out of different Blowfish modules in various languages. Is there one which can be relied upon as the standard?
For the following examples, assume the key is password and the plaintext 12345678.
a. The Online Encrypt Tool with Algorithm set to Blowfish mode ECB and Base64 Encode the output checked gives 2mADZkZR0VM=. I've been using this a my reference point, wise or otherwise.
b. The following Perl code uses Crypt::ECB and MIME::Base64
use MIME::Base64;
use Crypt::ECB;
$crypt = Crypt::ECB->new;
$crypt->padding(PADDING_NONE);
$crypt->cipher('Blowfish') || die $crypt->errstring;
$crypt->key('password');
$enc = $crypt->encrypt("12345678");
print encode_base64($enc);
This outputs 2mADZkZR0VM= with PADDING_NONE (which compares well with 'a.' above). However, when padding is set to PADDING_AUTO it outputs 2mADZkZR0VOZ5o+S6D3OZw== which is, in my mind at least, a bug as the plaintext is 8 characters long and in no need of padding.
c. If I use Crypt::Blowfish as below
#! c:\perl\bin
use Crypt::Blowfish;
use MIME::Base64;
my $key;
my $plaintext;
$key = "password";
$plaintext = "12345678";
my $cipher = new Crypt::Blowfish $key;
my $ciphertext = $cipher->encrypt($plaintext);
my $encoded = encode_base64( $ciphertext );
print $encoded;
then I get 2mADZkZR0VM= which matches 'a.' above. Trouble with this module though is that one has to segment things into 8 byte chunks for encoding; it has no chunker of its own.
d. If I use the source at http://linux.die.net/man/3/bf_ecb_encrypt (which I did for a recent PHP ext project) then I get the same answer as 'a.'. I'm inclined to trust this code the most as it's in use in SSLeay and OpenSSL.
e. The BlowfishEx.EXE in DI Management's Blowfish: a Visual Basic version with PKCS#5 padding gives 2mADZkZR0VOZ5o+S6D3OZw== which is the same as the Crypt::ECB results with PADDING_AUTO. With Padding set to None I get 2mADZkZR0VM= which matches 'a.'
I've partly answered my own question writing this: looks like I just have to modify DI Management's code for the VB6 project. And maybe suggest the same to the writer of Crypt::ECB.
But the question remains: is there a trustworthy blowfish reference platform?
Your problem doesn't seem to be with whether they implement the Blowfish algorithm correctly. In the "no padding" variant, they all seem to produce identical results.
That leaves a question of whether an 8-byte input should be padded at all. The answer to this is pretty simple: PKCS #7 requires that there is always at least one byte of padding added to the input. The reason for this is simple: on the receiving end, there needs to be an unambiguous way to remove the padding. In the case of PKCS#7, the value of the padding bytes is always the number of bytes of padding that needs to be stripped off by the recipient.
So, when you receive material that's been padded with PKCS7, you decrypt the block, then look at the last byte in the decrypted output, and remove that many bytes from the block. Without a block of padding on the end, the receiver would normally look at the last byte of actual data, and whatever value that byte happened to contain, strip off that many bytes (though it should probably tell you there's a problem if the number is larger than 8). In any case, to assure correct operation, there must always be at least one byte of padding. In your case, that means the input gets expanded out to 16 bytes instead of 8.

Categories