Last active
September 23, 2019 00:36
-
-
Save autotrof/daf77b8801164ab2d7d55c437f2446e1 to your computer and use it in GitHub Desktop.
AES encrypt-decrypt between php and javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//JAVASCRIPT | |
import CryptoJS from 'crypto-js'; | |
import { Base64 } from 'js-base64'; | |
const key = 'DK7Mf0GD1JR1Z3ROxeh+l/crGYKHVglLsInUN8Q8RiY='; | |
//function "unserialize" IS COPY FROM http://locutus.io/php/unserialize/ | |
const unserialize = data => { | |
var $global = typeof window !== 'undefined' ? window : global; | |
var utf8Overhead = function(str) { | |
var s = str.length; | |
for (var i = str.length - 1; i >= 0; i--) { | |
var code = str.charCodeAt(i); | |
if (code > 0x7f && code <= 0x7ff) { | |
s++; | |
} else if (code > 0x7ff && code <= 0xffff) { | |
s += 2; | |
} | |
if (code >= 0xdc00 && code <= 0xdfff) { | |
i--; | |
} | |
} | |
return s - 1; | |
}; | |
var error = function(type, msg, filename, line) { | |
throw new $global[type](msg, filename, line); | |
}; | |
var readUntil = function(data, offset, stopchr) { | |
var i = 2; | |
var buf = []; | |
var chr = data.slice(offset, offset + 1); | |
while (chr !== stopchr) { | |
if (i + offset > data.length) { | |
error('Error', 'Invalid'); | |
} | |
buf.push(chr); | |
chr = data.slice(offset + (i - 1), offset + i); | |
i += 1; | |
} | |
return [buf.length, buf.join('')]; | |
}; | |
var readChrs = function(data, offset, length) { | |
var i, chr, buf; | |
buf = []; | |
for (i = 0; i < length; i++) { | |
chr = data.slice(offset + (i - 1), offset + i); | |
buf.push(chr); | |
length -= utf8Overhead(chr); | |
} | |
return [buf.length, buf.join('')]; | |
}; | |
function _unserialize(data, offset) { | |
var dtype; | |
var dataoffset; | |
var keyandchrs; | |
var keys; | |
var contig; | |
var length; | |
var array; | |
var readdata; | |
var readData; | |
var ccount; | |
var stringlength; | |
var i; | |
var key; | |
var kprops; | |
var kchrs; | |
var vprops; | |
var vchrs; | |
var value; | |
var chrs = 0; | |
var typeconvert = function(x) { | |
return x; | |
}; | |
if (!offset) { | |
offset = 0; | |
} | |
dtype = data.slice(offset, offset + 1).toLowerCase(); | |
dataoffset = offset + 2; | |
switch (dtype) { | |
case 'i': | |
typeconvert = function(x) { | |
return parseInt(x, 10); | |
}; | |
readData = readUntil(data, dataoffset, ';'); | |
chrs = readData[0]; | |
readdata = readData[1]; | |
dataoffset += chrs + 1; | |
break; | |
case 'b': | |
typeconvert = function(x) { | |
return parseInt(x, 10) !== 0; | |
}; | |
readData = readUntil(data, dataoffset, ';'); | |
chrs = readData[0]; | |
readdata = readData[1]; | |
dataoffset += chrs + 1; | |
break; | |
case 'd': | |
typeconvert = function(x) { | |
return parseFloat(x); | |
}; | |
readData = readUntil(data, dataoffset, ';'); | |
chrs = readData[0]; | |
readdata = readData[1]; | |
dataoffset += chrs + 1; | |
break; | |
case 'n': | |
readdata = null; | |
break; | |
case 's': | |
ccount = readUntil(data, dataoffset, ':'); | |
chrs = ccount[0]; | |
stringlength = ccount[1]; | |
dataoffset += chrs + 2; | |
readData = readChrs(data, dataoffset + 1, parseInt(stringlength, 10)); | |
chrs = readData[0]; | |
readdata = readData[1]; | |
dataoffset += chrs + 2; | |
if (chrs !== parseInt(stringlength, 10) && chrs !== readdata.length) { | |
error('SyntaxError', 'String length mismatch'); | |
} | |
break; | |
case 'a': | |
readdata = {}; | |
keyandchrs = readUntil(data, dataoffset, ':'); | |
chrs = keyandchrs[0]; | |
keys = keyandchrs[1]; | |
dataoffset += chrs + 2; | |
length = parseInt(keys, 10); | |
contig = true; | |
for (i = 0; i < length; i++) { | |
kprops = _unserialize(data, dataoffset); | |
kchrs = kprops[1]; | |
key = kprops[2]; | |
dataoffset += kchrs; | |
vprops = _unserialize(data, dataoffset); | |
vchrs = vprops[1]; | |
value = vprops[2]; | |
dataoffset += vchrs; | |
if (key !== i) { | |
contig = false; | |
} | |
readdata[key] = value; | |
} | |
if (contig) { | |
array = new Array(length); | |
for (i = 0; i < length; i++) { | |
array[i] = readdata[i]; | |
} | |
readdata = array; | |
} | |
dataoffset += 1; | |
break; | |
default: | |
error('SyntaxError', 'Unknown / Unhandled data type(s): ' + dtype); | |
break; | |
} | |
return [dtype, dataoffset - offset, typeconvert(readdata)]; | |
} | |
return _unserialize(data + '', 0)[2]; | |
}; | |
const dec = encryptedString => { | |
let encrypted_json = JSON.parse(Base64.decode(encryptedString)); | |
let check1 = | |
typeof encrypted_json === 'object' && | |
encrypted_json.hasOwnProperty('iv') && | |
encrypted_json.hasOwnProperty('value') && | |
encrypted_json.hasOwnProperty('mac'); | |
if (!check1) throw new Error('payload tidak valid'); | |
let check2 = | |
CryptoJS.SHA256( | |
encrypted_json.iv + encrypted_json.value + key | |
).toString() == encrypted_json.mac; | |
if (!check2) throw new Error('mac tidak valid'); | |
let decrypted = CryptoJS.AES.decrypt( | |
encrypted_json.value, | |
CryptoJS.enc.Base64.parse(key), | |
{ | |
iv: CryptoJS.enc.Base64.parse(encrypted_json.iv), | |
} | |
); | |
let plainData = decrypted.toString(CryptoJS.enc.Utf8); | |
let validPlainData; | |
try { | |
validPlainData = unserialize(plainData); | |
} catch (e) { | |
validPlainData = plainData; | |
} | |
return validPlainData; | |
}; | |
const enc = data => { | |
data = JSON.stringify(data); | |
let encrypted = CryptoJS.AES.encrypt(data, CryptoJS.enc.Base64.parse(key), { | |
iv: CryptoJS.lib.WordArray.random(16), | |
}); | |
let value = encrypted.ciphertext.toString(CryptoJS.enc.Base64); | |
let iv = encrypted.iv.toString(CryptoJS.enc.Base64); | |
let mac = CryptoJS.SHA256(iv + value + key).toString(); | |
let hasil = Base64.encode(JSON.stringify({ iv, value, mac })); | |
return hasil; | |
}; | |
module.exports = { | |
key:key, | |
dec:dec, | |
enc:enc, | |
unserialize:unserialize | |
}; | |
//PHP | |
//THIS FUNCTION "is_serialized" COPY CODE FROM https://gist.github.com/cs278/217091 | |
if(!function_exists('is_serialized')){ | |
function is_serialized($value, &$result = null) | |
{ | |
// Bit of a give away this one | |
if (!is_string($value)) | |
{ | |
return false; | |
} | |
if ($value === 'b:0;') | |
{ | |
$result = false; | |
return true; | |
} | |
$length = strlen($value); | |
$end = ''; | |
if(isset($value[0])){ | |
switch ($value[0]) | |
{ | |
case 's': | |
if ($value[$length - 2] !== '"') | |
{ | |
return false; | |
} | |
case 'b': | |
case 'i': | |
case 'd': | |
// This looks odd but it is quicker than isset()ing | |
$end .= ';'; | |
case 'a': | |
case 'O': | |
$end .= '}'; | |
if ($value[1] !== ':') | |
{ | |
return false; | |
} | |
switch ($value[2]) | |
{ | |
case 0: | |
case 1: | |
case 2: | |
case 3: | |
case 4: | |
case 5: | |
case 6: | |
case 7: | |
case 8: | |
case 9: | |
break; | |
default: | |
return false; | |
} | |
case 'N': | |
$end .= ';'; | |
if ($value[$length - 1] !== $end[0]) | |
{ | |
return false; | |
} | |
break; | |
default: | |
return false; | |
} | |
} | |
if (($result = @unserialize($value)) === false) | |
{ | |
$result = null; | |
return false; | |
} | |
return true; | |
} | |
} | |
//ENCRYPT | |
if(! function_exists('enc')){ | |
function enc($plainData,$serializing = true,$secrete_key=null){ | |
if($secrete_key===null) $secrete_key = substr(env('APP_KEY'),7);//USE YOUR OWN ENV IF THERE IS NO KEY | |
$key = base64_decode($secrete_key); | |
$length = mb_strlen($key, '8bit'); | |
$cipher = $length===16?'AES-128-CBC':'AES-256-CBC'; | |
$iv = base64_encode(random_bytes(openssl_cipher_iv_length($cipher))); | |
if(is_array($plainData)) $serializing = true; | |
if($serializing){ | |
$data = serialize($plainData); | |
}else{ | |
$data = $plainData; | |
} | |
$value = openssl_encrypt($data, $cipher, $key, 0, base64_decode($iv)); | |
if ($value === false) throw new Exception('Tidak dapat mengenkripsi data'); | |
$mac = hash('sha256', $iv.$value.$secrete_key); | |
$json = json_encode(['iv'=>$iv,'value'=>$value,'mac'=>$mac]); | |
if (json_last_error() !== JSON_ERROR_NONE) throw new Exception('Tidak dapat mengenkripsi data', 1); | |
return base64_encode($json); | |
} | |
} | |
//DECRYPT | |
if(! function_exists('dec')){ | |
function dec($encryptedData,$secrete_key=null){ | |
if($secrete_key===null) $secrete_key = substr(env('APP_KEY'),7);//USE YOUR OWN ENV IF THERE IS NO KEY | |
$key = base64_decode($secrete_key); | |
$length = mb_strlen($key, '8bit'); | |
$payload = (array)json_decode(base64_decode($encryptedData)); | |
$cipher = $length===16?'AES-128-CBC':'AES-256-CBC'; | |
$validPayload = is_array($payload) && isset($payload['iv'], $payload['value'], $payload['mac']) && | |
strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length($cipher); | |
if(!$validPayload) throw new Exception("Payload tidak valid", 1); | |
$validMac = hash('sha256', $payload['iv'].$payload['value'].$secrete_key)===$payload['mac']; | |
if(!$validMac) throw new Exception("Mac tidak valid", 1); | |
$iv = base64_decode($payload['iv']); | |
$decrypted = openssl_decrypt($payload['value'], $cipher, $key, 0, $iv); | |
if ($decrypted === false) throw new Exception("Tidak dapat membaca data", 1); | |
$plainData = is_serialized($decrypted)?unserialize($decrypted):$decrypted; | |
$realPlainData = json_decode($plainData,true); | |
if (json_last_error() !== JSON_ERROR_NONE){ | |
return $plainData; | |
}else{ | |
return $realPlainData; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment