PHP Mcrypt and HTML5 Crypto API encryption/decryption - javascript

I need to exchange a series of keys for an external platform through a webpage, may happens that user has not a SSL cert configured for use HTTPS so for do it in a secure way before starting the transfer I send a confirmation code to the user mail box, with this code I generate a SHA-256 hash (done with PHP and HTML5 Crypto API) and I use this as key for AES-256 (CBC) encryption of the string with keys, after that I convert it in base64 and send to the client that will convert it into file and triggers download. The problem is that I can't convert the components (the IV is connected to the encrypted string) into the format requested by JavaScript. Here the decryption code:
string = window.atob(string);
promise_key = window.crypto.subtle.generateKey({name: "AES-CBC", length: 256}, false, ["decrypt"]);
promise_key.then(function(key){
key_object = key;
var vector = string.substr(0, 16);
string = string.substr(16);
decrypt_promise = window.crypto.subtle.decrypt({name: "AES-CBC", iv: vector, length: 256}, key_object, string);
decrypt_promise.then(function(result){
decrypted_data = new Uint8Array(result);
console.log(decrypted_data);
});
});
Process:
Client sends an AJAX request to the server for init the key generation;
Server send a confirmation numeric code to user mail box and an ID to AJAX;
User receives the e-mail message, writes code into web page and submits it;
Client sends an AJAX request for the step 2 with the reveived ID;
Server checks ID, generates and encrypts keys and sends them to the client;
Client prepares the decryption key hashing the e-mail code with SHA-256 (also the server done this for encryption);
Client decrypts keys, build a JSON string and trigger the download as file.

Related

How does this API endpoint validate the request is legit?

I ran across a list of open API endpoints a while back, and took notice of this one because of the way it is called.
https://developer.marvel.com/documentation/authorization
Here are the rules for sending a request:
Authentication for Server-Side Applications
Server-side applications must pass two parameters in addition to the apikey parameter:
ts - a timestamp (or other long string which can change on a request-by-request basis)
hash - a md5 digest of the ts parameter, your private key and your public key (e.g. md5(ts+privateKey+publicKey)
For example, a user with a public key of "1234" and a private key of "abcd" could construct a valid call as follows:
http://gateway.marvel.com/v1/public/comics?ts=1&apikey=1234&hash=ffd275c5130566a2916217b101f26150
(the hash value is the md5 digest of 1abcd1234)
My Question
How does their server validate such a request? I am using a TIMESTAMP, but it is being hashed - how do they know my request is legit?
So, for fun and games, let's assume we are responsible for validating that call, in - let's say, Javascript (PsuedoCode):
/* Grab and Parse Querystring */
let ts = "1";
let publicKey = "1234"; // Public Key
let hash = "ffd275c5130566a2916217b101f26150"; //md5 ts+ privateKey + publicKey
// Ok. now what???
In this case, there is a "shared secret", and that is what they're calling the "private key".
Basically, they use the apikey parameter to look up what the secret is in their database. Then, they re-hash to ensure that the hash matches.
The reason for the timestamp is to prevent replay attacks. However, from what information you're saying, it doesn't appear that they're actually validating the request data itself (URL, parameters, etc.). So, someone who gets a request URL could make other requests with that same hash if they do so quickly. This is not a very good practice.
The timestamp and public key are both in the query parameters along with the hash. The server can look up the private key associated with the public key in the request, generate a hash, and compare that with the value you provided.
(The public key and private key are issued by the server as a pair, so it already knows the private key.)

AES encryption in JS, decrypt in PHP?

When my form gets submitted, it will first make a request to this controller action to get the server's public key:
public function preprocessPayment(Request $request) {
// Get public key
$publicKey = $this->EncryptionService->getPublicKey();
// Generate iv
$method = 'aes-256-cbc';
$ivlen = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($ivlen);
return response()->json([
'success' => true,
'data' => [
'public_key' => $publicKey,
'iv' => $iv
]
]);
}
After that, in my client, I'm going to generate a secret key using AES via CryptoJS, that will later be encrypted with the public_key.
Then, the form data will be encrypted in AES using the AES secret key, and then the following payload will be submitted to the server:
{
secret_key: xxx,
iv: xxx,
form_data: {...}
}
The AES encrypted data will be processed here:
public function storePayment(Request $request) {
// Decrypt AES secret key (that was encrypted with the RSA public key),
// using RSA private key
// Decrypt AES client data with secret key
// Store data in database
}
My question is, how will I do the AES secret key generation and encryption on the client side using CryptoJS? Could not seem to find any good documentation about it. How should I format the data so it will be accepted by the server for decryption?
And I'm stuck with decrypting AES in PHP, because it requires a $tag and I don't know where to get that when everything is coming from the client.
$originalData = openssl_decrypt($data, 'aes-128-gcm', $secretKey, $options=0, $iv, $tag);
I found this link: http://cryptojs.altervista.org/js-php/, but I'm not sure how to make it work because I'm not sure where to locate the needed scripts.
Edit:
I made a mistake, for decrypting on the server, I was using aes-128-gcm instead of aes-256-cbc. When I corrected it, I was able to decrypt without the $tag.
An AES-256 key is nothing more than 32 random bytes. So you create the key by using a cryptographically secure random number generator.
However, both RSA PKCS#1 v1.5 and AES-CBC are vulnerable to padding oracle attacks. So not only can an adversary change the message, the message is also not kept confidential. In other words, you can use 256 bit keys as much as you want, but you should not create your own transport protocol, because the perceived security just isn't there.
You could sign the ciphertext, but that has problems as well - generally we sign then encrypt.
Use TLS.

How to encrypt data in browser with JavaScript and decrypt on server side with Node.js

I'm trying to encrypt a message using AES256 on the browser, send it to the server, and then decrypt it but I keep getting this error server-side :
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
I tried for hours to find a solution but I don't see where is the issue. I'm using crypto-js for client-side and the standard library for Node.js crypto
This is the code sample that I'm using on client-side.
import * as CryptoJS from 'crypto-js';
const secret = 'example';
const passphrase = 'secret-passphrase'
const encrypted = CryptoJS.AES.encrypt(secret, passphrase);
const encrypted64 = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
// make a request to a Node.js API with the password encrypted
This is the code sample that I'm using on server-side.
const crypto = require('crypto');
const secret = req.body.secret;
const passphrase = 'secret-passphrase'
const decipher = crypto.createDecipher('aes256', passphrase);
let decrypted = decipher.update(secret, 'base64', 'utf8');
decrypted += decipher.final('utf8');
Any idea?
Thanks.
Using HTTPS will encrypt the payload during the transfer and have it decrypted at the server. HTTPS uses RSA to encrypt the keys used in the encryption of the message.
RSA uses 1024 bit key value which is difficult to crack. The hacker has to factor large numbers into their original prime values to get the keys which makes its almost impossible to crack.
It is generally advisable to use HTTPS in your transmissions.
The error generally occurs when you use the wrong key.
You are using different packages to begin with.
crypto-js will take your passphrase, create a key and encrypt cleartext that you have in secret.
crypto will do the same when you create Decipher, but then there's the issue of padding. In short, key is not same for crypto when decrypting.
Use a 32 bytes key like abcabcabc1abcabcabc1abcabcabc132
Go for client-side encryption like:
var ciphertext= C.AES.encrypt(secret, C.enc.Hex.parse(passphrase), { mode: C.mode.ECB, padding: C.pad.NoPadding }).ciphertext.toString();
And on your server side code go for following (after passing the same passphrase to decipher):
let decrypted = decipher.update(ciphertext, 'hex', 'utf8');
Give it a try. It should work. It will be easier if you use crypto-js on both client and server.
Hope it helps!

Is it possible to send binary data with STOMP over WebSockets using Spring-WebSockets?

I am able to send and receive JSON with STOMP over WebSockets following spring documentation. However performance is poor at large high rates, so I wish to profile the use of binary messages.
Spring-WebSockets 4.0
JavaScript client running in Chrome 35
stomp.js 1.7.1
Sending
I send messages using SimpMessageTemplate with the necessary broker relay - see spring documentation
#Controller
public class DemoBinaryController {
#Autowired
private SimpMessagingtemplate template
#Scheduled(fixedDelay = 5000)
public void demo() throws Exception {
GenericMessage<byte[]> message = new GenericMessage<byte[]>(new byte[]{65,66,67});
template.send("/app/binarydemo", message);
}
}
Receiving
A JavaScript client receives data using stomp.js using the standard mechanism.
var subscription = client.subscribe("/app/binarydemo", new function(message) {
console.log("RX message", typeof message.body, message.body.length);
});
Messages are received, but as Strings, with console output as follows. I'm expecting some raw type, like ArrayBuffer
RX message string 3
RX message string 3
Things I've tried
I realise the T in STOMP stands for Text, however the Spring documentation implies binary messages are possible at least with plain WebSockets, also the stomp specification states
STOMP is text based but also allows for the transmission of binary
messages.
Debugging the sending code, and it appears to remain as byte [] for as far as I can see
Debugging the stomp.js library whilst receiving. The message appears to be a String when received in the underlying ws.onmessage callback (line 243 in stomp-1.7.1.js)
Lots of searching - this seems a rare topic with little information
Looking at the stomp.js source code. The only reference to binary is ws.binaryType = "arraybuffer".
Update: I've done more debugging on the server side. It seems that org.springframework.web.socket.TextMessage is always used within org.springframework.web.socket.messaging.StompSubProtocolHandler rather than org.springframework.web.socket.BinaryMessage. I've raised a feature request for this SPR-12301
message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
byte[] bytes = this.stompEncoder.encode((Message<byte[]>) message);
synchronized(session) {
session.sendMessage(new TextMessage(new String(bytes, UTF8_CHARSET)));
}
My question
Is this approach possible with this mix of technologies?
Am I missing some crucial step?
Can anyone point me to a working example
It seems that org.springframework.web.socket.TextMessage is always used within org.springframework.web.socket.messaging.StompSubProtocolHandler rather than org.springframework.web.socket.BinaryMessage. I've raised a feature request for this SPR-12301 which has been accepted.
message = MessageBuilder.withPayload(message.getPayload()).setHeaders(headers).build();
byte[] bytes = this.stompEncoder.encode((Message<byte[]>) message);
synchronized(session) {
session.sendMessage(new TextMessage(new String(bytes, UTF8_CHARSET)));
}
Update
SPR-12301 was delivered in 4.1.2 but only adds support for receiving binary messages
Raised SPR-12475 for sending of binary messages
Try to configure you Server just with ByteArrayMessageConverter:
#Configuration
#EnableWebSocketMessageBroker
public class MyWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add(new ByteArrayMessageConverter());
return false;
}
}
UPDATE
Ah! I see that. Thanks:
public TextMessage(byte[] payload) {
super(new String(payload, UTF_8));
this.bytes = payload;
}
From other side from STOMP spec:
The body of a STOMP message must be a String. If you want to send and receive JSON objects, ...
According to the ArrayBuffer:
Getting an array buffer from existing data
From a Base64 string
From a local file
So, I think you don't have a chioce rather than provide you some custom MessageConverter, which converts your byte[] to Base64 String and send it.
On the JavaScript side you should decode that string to the ArrayBuffer somehow.

Issues running crypto-js on Parse Cloud Code for iOS application

I'm using Parse as my backend for my iOS application and would like to encrypt all the data that's sent between Parse and my iOS device. As such, I'm using Parse Cloud Code in hopes of being able to perform server-side encryption & decryption to process all data it sends and receives.
Apparently Parse has a 'crypto' module by default, but since I've been unable to find any documentation for it, I've gone ahead and tried using crypto-js by copying the appropriate files for AES encryption + decryption into my Parse Cloud Code /cloud folder.
The issue I'm running into is that I'm not sure what class of object is being returned by crypto-js's AES encryption / decryption function. I *seem* to be getting back an NSDictionary object but have no idea what to do with it. I would have guessed that I would receive an NSString or NSData object, but I seem to have guessed wrong.
Please let me know what additional information I can provide or what incorrect assumptions I may have made.
I needed to encrypt / decrypt on the server side, here is my cloud code which is similar to nodeJS code:
var crypto = require('crypto');
var cryptoAlgorithm = "aes-128-cbc"; //or whatever you algorithm you want to choose see http://nodejs.org/api/crypto.html
var cryptoPassword = "theLongAndRandomPassphrase";
var cipher = crypto.createCipher(cryptoAlgorithm,cryptoPassword);
var decipher = crypto.createDecipher(cryptoAlgorithm,cryptoPassword);
exports.myCiphering = {
encrypt:function(text){
var encrypted = cipher.update(text,'utf8','hex')
encrypted += cipher.final('hex');
return encrypted;
},
decrypt: function(text){
var decrypted = decipher.update(text,'hex','utf8')
decrypted += decipher.final('utf8');
return decrypted;
}
};
If this snippet has been saved in "cloud/ciphering.js", you can then use the ciphering tool like this anywhere in cloud code:
var text = "encryptMe";
var ciphering = require("cloud/ciphering.js").myCiphering;
var encrypted = ciphering.encrypt(text);
var decrypted = ciphering.decrypted(encrypted);
if (decrypted == text){
//the "password" is correct
}
Since Parse uses SSL all data is sent encrypted, SSL is enough to secure the communications.
You may want to encrypt the data so that it is protected on the server but unless you really understand cryptographic security don't.
Plain or encrypted passwords should never be stored, store a properly salted and hashed version of the password.
If you feel your data is substantially valuable enough have a security domain expert design it. Getting security right is hard, one mistake will invalidate it all.

Categories