Jump to content

aes js & php


Destramic
Go to solution Solved by Jacques1,

Recommended Posts

after more help from jacques1 i've been tring to use nodejs's crypto...i want to be able to encryt data via js and decrypt via php and visa versa.

 

in my aes.js i have created function so i can encrypt and decrypt data although i get an error on both and i'm completely stuck!

 

 

[Error: Invalid IV length]
decrypt null
[Error: Invalid IV length]
encrypt null

 

 

to be honest all this encrypting and decrypting is quite confusing to say the least and could really do with some more help...the iv length is set at 32 which is obviously too shore or too long...where am i going wrong here please?

 

here is all my code that i am using for this

 

 

test.js

var aes = require('./aes'),
    master_key = "5e2a0626516f3108e55e25e4bb6a62835c2f5d2b2b8d194c9acca63ef8beff6bfb947233bd83cfda9021e5a80bc183bcd835180c9955b733fd1a6d9d";

var decrypt = aes.decrypt("yUB2jQe88yWT2yUNHstMCw==", "2a3dc736bc316e9a20566b9a108eb23a", master_key);
console.log('decrypt', decrypt);

var encrypted = aes.encrypt('destramic', master_key);
console.log('encrypt', encrypted);

// array from aes.php encrypted and decrypted.

//Array
//(
//    [0] => Array
//        (
//            [name] => yUB2jQe88yWT2yUNHstMCw==
//            [name_init_vector] => 2a3dc736bc316e9a20566b9a108eb23a
//            [age] => PjDxZq2x5IrhsLcqaJd5JQ==
//            [age_init_vector] => 01b53388ddcd8352db17e30f44e9b9e8
//        )
//
//    [1] => Array
//        (
//            [name] => LRxu9zAdlkSgS+wmyi4PrQ==
//            [name_init_vector] => 1c3d3bf95168ab025ca5c63ec3926313
//            [age] => ys6yPHqjtutq/PJ5G3r0FQ==
//            [age_init_vector] => f19b6975eece8d8995e86b0ee6a33569
//        )
//
//)
//Array
//(
// [0] => Array
//       (
//            [name] => destramic
//            [age] => 28
//        )
//
//    [1] => Array
//        (
//            [name] => alan
//            [age] => 99
//        )
//
//)

aes.js - where my problem ara...

var crypto                = require('crypto'),
    encryption_algorithem = 'AES-128-CBC';
    
module.exports = {
    encrypt: function (text, master_key){
        try {
            var init_vector = new Buffer(crypto.randomBytes(32));

            ciphertext  = crypto.createCipheriv(encryption_algorithem, master_key, init_vector);
            init_vector = init_vector.update('hex');

        	return {'ciphertext' : ciphertext,
        		    'init_vector' : init_vector
        	}; 
        }catch(error){
        	console.log(error);
        	
        	return null;
        }
    },

    decrypt: function (ciphertext, init_vector, master_key){
        try {
        	 var decipher  = crypto.createDecipheriv(encryption_algorithem, master_key, init_vector),
                 decrypted = decipher.update(ciphertext, 'bin');

             return decrypted;

        }catch(error){
        	console.log(error);
        	
        	return null;
        }
    }
};

aes.php

<?php

const ENCRYPTION_ALGORITHM = 'AES-128-CBC';

class AES
{
    private $_master_key;
    
    public function __construct()
    {
        $this->_master_key = "5e2a0626516f3108e55e25e4bb6a62835c2f5d2b2b8d194c9acca63ef8beff6bfb947233bd83cfda9021e5a80bc183bcd835180c9955b733fd1a6d9d";
    }
    
    public function generate_master_key($length = 64)
    {
        if (!is_numeric($length))
        {
            return null;
        }
        
        $max_attempts = 10;
        $attempts     = 0;
          
        do
        {
            $bytes = openssl_random_pseudo_bytes($length, $cryptographically_strong);
            
            $attempts++;
        }
        
        while (!$cryptographically_strong && $attempts < $max_attempts);

        if (!$cryptographically_strong)
        {
            return false;
        }
        
        $hex = bin2hex($bytes);
        
        return $hex;
    }
    
    public function encrypt($value, $master_key)
    {
        $init_vector = openssl_random_pseudo_bytes(openssl_cipher_iv_length(ENCRYPTION_ALGORITHM));
        $ciphertext  = openssl_encrypt($value, ENCRYPTION_ALGORITHM, $master_key, false, $init_vector);
       
        return array(
            'init_vector' => bin2hex($init_vector),
            'ciphertext'  => $ciphertext
        );
    }
    
    public function decrypt($ciphertext, $init_vector, $master_key)
    {
        $plaintext = openssl_decrypt($ciphertext, ENCRYPTION_ALGORITHM, $master_key, false, hex2bin($init_vector));
    
        return $plaintext;
    }
    
    public function encrypt_array($array)
    {
        $encrypted_array = array();
        $master_key      = $this->_master_key;
        
        foreach ($array as $key => $data)
        {
            foreach ($data as $column => $value)
            {
                $encryption         = $this->encrypt($value, $master_key);
                $init_vector_column = $column . '_init_vector';
                
                $encrypted_array[$key][$column]             = $encryption['ciphertext'];
                $encrypted_array[$key][$init_vector_column] = $encryption['init_vector'];
            }
        }
        
        return $encrypted_array;
    }
    
    public function decrypt_array($array)
    {
        $decrypted_array = array();
        $master_key      = $this->_master_key;
        
        foreach ($array as $key => $data)
        {
            foreach ($data as $column => $value)
            {
                $init_vector = $column . '_init_vector';
        
                if (array_key_exists($init_vector, $data))
                {
                    $init_vector = $data[$init_vector];
        
                    $decrypted_value                = $this->decrypt($value, $init_vector, $master_key);
                    $decrypted_array[$key][$column] = $decrypted_value;
                }
            }
        }
        
        return $decrypted_array;
    }
}

$aes = new AES;

$data = array(
         array('name' => 'destramic', 
               'age' => '28'),
         array('name' => 'alan',
               'age' => '99')    
);

$encryption = $aes->encrypt_array($data);
print_r($encryption);
$decryption = $aes->decrypt_array($encryption);
print_r($decryption);

i hope someone can educate me a bit more here...thank you

Edited by Destramic
Link to comment
Share on other sites

  • Solution

Both the key and the IV must be raw, unencoded strings with exactly 128 bits (or 16 bytes). Right now, all your strings are hex-encoded and also way too long.

const CRYPTO = require('crypto');

const CIPHER = "AES-128-CBC";

const MASTER_KEY_HEX = "5e2a0626516f3108e55e25e4bb6a6283";
const MASTER_KEY = new Buffer(MASTER_KEY_HEX, "hex");



// encrypt
var plaintext = new Buffer("attack at dawn");
var initVector = CRYPTO.randomBytes(16);
var cipher = CRYPTO.createCipheriv(CIPHER, MASTER_KEY, initVector);
var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);

console.log( ciphertext.toString("hex") );



// decrypt
var decipher = CRYPTO.createDecipheriv(CIPHER, MASTER_KEY, initVector);
var decryptedPlaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);

console.log( decryptedPlaintext.toString() );
Link to comment
Share on other sites

ok thank you....but when generating a key from my generate_master_key() in my aes.php i'm able to use any number of bytes i wish? and it encrypts fine :confused:

 

the code works perfect also thanks :happy-04:

 

i altered a bit so the init vector returns as hexidecimal after used for encrypting, so it's the same as my php version and then returned back to binary afterwards before decrypting.

const CRYPTO = require('crypto');
const CIPHER = "AES-128-CBC";                      
const MASTER_KEY_HEX = "28c03478bbd1874d5c472dd5d6f00ca0";
const MASTER_KEY = new Buffer(MASTER_KEY_HEX, "hex");

console.log('master key', MASTER_KEY);


// encrypt
var plaintext = new Buffer("attack at dawn");
var initVector = CRYPTO.randomBytes(16);
var cipher = CRYPTO.createCipheriv(CIPHER, MASTER_KEY, initVector);
var ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);

console.log('ciphertext ' + ciphertext.toString("hex") + '    init_vector ' + initVector.toString('hex'));


// decrypt
initVector = initVector.toString('binary');
var decipher = CRYPTO.createDecipheriv(CIPHER, MASTER_KEY, initVector);
var decryptedPlaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);

console.log(decryptedPlaintext.toString());

 

master key <Buffer 28 c0 34 78 bb d1 87 4d 5c 47 2d d5 d6 f0 0c a0>
ciphertext bf8d6421f518dac424cac43a7a3e25f2    init_vector a73251f1bcad23e6bf95b1b8ed41dc96
attack at dawn

 

although the problem i have is when trying to decrypt the data i encrypted in js via my php as it returns fail.

$master_key  = "28c03478bbd1874d5c472dd5d6f00ca0";
$encryption  = "AES-128-CBC";    
$ciphertext  = "bf8d6421f518dac424cac43a7a3e25f2";
$init_vector = "a73251f1bcad23e6bf95b1b8ed41dc96";

$data = openssl_decrypt($ciphertext, $encryption, $master_key, false, hex2bin($init_vector));

if ($data)
{
    echo $data;   
}
else
{
    echo "fail";   
}

i'd say the problem is using

const MASTER_KEY = new Buffer(MASTER_KEY_HEX, "hex");

which then changes key to <Buffer 28 c0 34 78 bb d1 87 4d 5c 47 2d d5 d6 f0 0c a0>  instead of 28c03478bbd1874d5c472dd5d6f00ca0

 

could you please tell me what i'm doing wrong here

 

thank you for your help

Link to comment
Share on other sites

It's a problem with your PHP code, not the NodeJS code. The master key buffer is perfectly fine. This “<Buffer ...>” stuff is just a visual representation. Internally, a NodeJS buffer is a sequence of raw bytes (which also means that you don't need initVector.toString('binary'); just pass initVector straight to the function).

 

In your PHP code, you're still using the hex representation of the ciphertext and master key. You need the raw binary data, so apply hex2bin() to those parameters as well. You also need to set the OPENSSL_RAW_DATA flag. Otherwise, openssl_decrypt() expects a Base64-encoded string, which doesn't make sense in your case.

<?php

$master_key_hex  = "28c03478bbd1874d5c472dd5d6f00ca0";
$cipher  = "AES-128-CBC";
$ciphertext_hex  = "bf8d6421f518dac424cac43a7a3e25f2";
$init_vector_hex = "a73251f1bcad23e6bf95b1b8ed41dc96";

$plaintext = openssl_decrypt(hex2bin($ciphertext_hex), $cipher, hex2bin($master_key_hex), OPENSSL_RAW_DATA, hex2bin($init_vector_hex));

var_dump($plaintext);

I strongly recommend that you append a “_hex” suffix to all hex-encoded strings so that you won't confuse them with the actual binary data.

 

 

 

ok thank you....but when generating a key from my generate_master_key() in my aes.php i'm able to use any number of bytes i wish? and it encrypts fine :confused:

 

OpenSSL silently truncates overly long keys, but you should still use the correct length.
 

Link to comment
Share on other sites

ok will you help i reviewed the way i was doing things as i wanted to use PKCS#7 instead of OPENSSL_RAW_DATA as i read it is the best method?

 

here is my working (i still have to tidy up a few things with it though) code which is compatible js - php and visa versa...hopefully it can help someone else out who is trying to learn aes and how to encrypt thier data the right way.

 

 

 

 

js version aes.js

var crypto = require('crypto');

$encryption_algorithm = 'AES-128-CBC';
$bytes                = 16;
$master_key           = null;

module.exports = {
    generate_master_key: function (length){
        try {
            if (typeof length != 'number'){
                throw 'Error: Master key length must be numberic.';
            }
            
            var bytes = length / 2;
            
            return $master_key = crypto.randomBytes(length).toString('hex');
        }
        catch (error){
            console.log(error);
            
            return null;
        }
    },
    
    master_key: function (master_key){
        $master_key = master_key;
    },
    
    encryption: function (bytes){
        try{
            if (typeof bytes != 'number'){
                throw 'Error: Encryption bytes size must be numberic.';
            }
        
            $bytes   = bytes;
            var bits = bytes * 8;
            
            $encryption_algorithm = 'AES-' + bits + '-CBC';
        }
        catch (error){
            console.log(error);
            
            return null;
        }
    },
    
    encrypt: function (data){
        if (Object.prototype.toString.call(data) === '[object Object]' ||
            Object.prototype.toString.call(data) === '[object Array]'){
            var encrypted = {};
            
            if (Object.prototype.toString.call(data) === '[object Array]'){
                encrypted = [];
            }
            
            for (key in data){
                if (Object.prototype.toString.call(data[key]) === '[object Object]' ||
                    Object.prototype.toString.call(data[key]) === '[object Array]'){
                    encrypted[key] = {};
                    
                    if (Object.prototype.toString.call(data[key]) === '[object Array]'){
                        var array = [];
                        
                        for (i in data[key]){                
                            array.push(encrypt_data(data[key][i]));
                        }
                                    
                        encrypted[key] = array;
                    }
                    else {
                        for (key2 in data[key]){
                            if (Object.prototype.toString.call(data[key][key2]) === '[object Array]'){
                                var array = [];
                                
                                for (i in data[key][key2]){                
                                    array.push(encrypt_data(data[key][key2][i]));
                                }
                                            
                                encrypted[key][key2] = array;
                            }
                            else {
                                encrypted[key][key2] = encrypt_data(data[key][key2]);
                            }
                        }
                    }
                }
                else {
                    if (Object.prototype.toString.call(encrypted) === '[object Array]'){
                        encrypted.push(encrypt_data(data[key]));
                    }
                    else {
                        encrypted[key] = encrypt_data(data[key]);
                    }
                }
            }
            
            return encrypted;
        }
        else {
            return encrypt_data(data);
        }
        
        return data;
    },

    decrypt: function (ciphertext, init_vector){
        if (typeof ciphertext != 'undefined' &&
            typeof init_vector != 'undefined'){
        }
        else if (Object.prototype.toString.call(ciphertext) === '[object Object]' ||
                 Object.prototype.toString.call(ciphertext) === '[object Array]'){
            var data = ciphertext;
            
            if (data['ciphertext'] &&
                data['init_vector']){
                return decrypt_data(data['ciphertext'], data['init_vector']);
            }
            else {
                var decrypted = {};
                
                if (Object.prototype.toString.call(data) === '[object Array]'){
                    decrypted = [];
                }
                
                for (key in data){
                    if (data[key]['ciphertext'] &&
                        data[key]['init_vector']) {
                        if (Object.prototype.toString.call(decrypted[key]) === '[object Array]'){
                            decrypted.push(decrypt_data(data[key]['ciphertext'], data[key]['init_vector']));
                        }
                        else {
                            decrypted[key] = decrypt_data(data[key]['ciphertext'], data[key]['init_vector']);
                        }
                    }
                    else if (Object.prototype.toString.call(data[key]) === '[object Object]' ||
                             Object.prototype.toString.call(data[key]) === '[object Array]'){
                        decrypted[key] = {};
                        
                        for (key2 in data[key]){
                            if (data[key][key2]['ciphertext'] &&
                                data[key][key2]['init_vector']) {
                                
                                if (Object.prototype.toString.call(data[key]) === '[object Array]'){
                                    var array = [];
                                    
                                    for (i in data[key]){    
                                        array.push(decrypt_data(data[key][i]['ciphertext'], data[key][i]['init_vector']));
                                    }
                                                
                                    decrypted[key][key2] = array;
                                }
                                else {
                                    decrypted[key][key2] = decrypt_data(data[key][key2]['ciphertext'], data[key][key2]['init_vector']);
                                }
                            }
                            else{
                                decrypted[key][key2] = data[key][key2];
                            }
                        }
                    }
                    else {
                        decrypted.push(data[key]);
                    }
                }
            }
            
            return decrypted;
        }

        return null;
    }
};

function encrypt_data(data){
    try {
        data = new Buffer(data);
        
        var init_vector = crypto.randomBytes($bytes),
            cipher      = crypto.createCipheriv($encryption_algorithm, $master_key, init_vector),
            ciphertext  = Buffer.concat([cipher.update(data), cipher.final()]);
        
        return ({'ciphertext'  : ciphertext.toString('hex'),
                 'init_vector' : init_vector.toString('hex')
            });
    }
    catch(error){
        console.log(error);
        
        return null;
    }
}

function decrypt_data(ciphertext, init_vector){
     try {
         ciphertext =  new Buffer(ciphertext, "hex");
         init_vector = new Buffer(init_vector, "hex");

         var decipher = crypto.createDecipheriv($encryption_algorithm, $master_key, init_vector),
             data     = Buffer.concat([decipher.update(ciphertext), decipher.final()]);

         return data.toString();
     }
     catch(error){
         console.log(error);
         
         return null;
     }
}

test.js

var aes = require('./aes');

aes.master_key('e16cd89767a18c53');

console.log(aes.decrypt({
	    1: { 'name' : {"ciphertext":"96c0c88e0c9278122dbf50ca0723d9d3","init_vector":"5cf483f898caced497d8c77600245e20"},
              'age' : {"ciphertext":"d64a9d200cea8a15faca7a2f111e98cb","init_vector":"e14c56b1fa718be8f18ce09eb2759b1f"}
	       },
        2: { 'name' :  {"ciphertext":"55508883c450d78e676fa0bd8f40f8d3","init_vector":"957d7a2f9587672dbc706c0ac9dd6bba"},
              'age' : {"ciphertext":"888fe201c53f390ce634d0980ce7ffa7","init_vector":"0e2e10dd53d1807bfbb36020d851e2a6"}
           }
}));
 

php version

   <?php

class AES
{
    private $_master_key;
    private $_encryption_algorithm = 'AES-128-CBC';
    private $_bytes                = 16;
    
    public function generate_master_key($length = 16)
    {
        if (!is_numeric($length))
        {
            return null;
        }
        
        $length      /= 2;
        $max_attempts = 10;
        $attempts     = 0;
          
        do
        {
            $bytes = openssl_random_pseudo_bytes($length, $cryptographically_strong);
            
            $attempts++;
        }
        
        while (!$cryptographically_strong && $attempts < $max_attempts);

        if (!$cryptographically_strong)
        {
            return false;
        }
        
        return bin2hex($bytes);
    }

    public function excryption($bytes)
    {
        try
        {
            $this->_bytes = bytes;
            $bits         = bytes * 8;
             
            $_encryption_algorithm = 'AES-' + bits + '-CBC';
        }
        catch (Exception $exception)
        {
            echo $exception->getMessage();
        }
        
    }
    
    public function master_key($master_key)
    {
        $this->_master_key = $master_key;
    }
    
    private function encrypt_data($data)
    {
        $init_vector = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->_encryption_algorithm));
        $ciphertext  = openssl_encrypt($data, $this->_encryption_algorithm, $this->_master_key, false, $init_vector);
       
        return json_encode(array('ciphertext'  => bin2hex(base64_decode($ciphertext)),
                                 'init_vector' => bin2hex($init_vector)                  
        ));
    }
    
    private function decrypt_data($data, $init_vector = null)
    {
        if ($this->is_json($data) && is_null($init_vector))
        {
            $data = json_decode($data);

            if (isset($data->ciphertext) &&
                isset($data->init_vector))
            {
               $ciphertext  = $data->ciphertext;
               $init_vector = $data->init_vector;
            }
        }

        if (isset($ciphertext) &&
            isset($init_vector))
        {
            return openssl_decrypt(base64_encode(hex2bin($ciphertext)), $this->_encryption_algorithm, $this->_master_key, false, hex2bin($init_vector));    
        }
    }
    
    private function is_json($data)
    {
        if (is_string($data) &&
            is_array(json_decode($data, true)) &&
            (json_last_error() === JSON_ERROR_NONE))
        {
            return true;
        }
        
        return false;
    }
    
    public function encrypt($data)
    {
        try
        {
            if (is_array($data))
            {
                $encrypted_array = array();
                
                foreach ($data as $key => $value)
                {
                    if (is_array($value))
                    {
                        foreach ($value as $key2 => $value2)
                        {
                            $encrypted_array[$key][$key2] = $this->encrypt($value2);
                        }
                    }
                    else
                    {
                        $encrypted_array[$key] = $this->encrypt($value);
                    }
                }
                
                return $encrypted_array;
            }
            else
            {
                return $this->encrypt_data($data);
            }
        }
        catch (Exception $exception)
        {
            echo $exception->getMessage();
        }
    }
    
    public function decrypt($data, $init_vector = null)
    {
        try
        {
            if (is_array($data))
            {
                $decrypted_array = array();
                
                foreach ($data as $key => $value)
                {
                    if (is_array($value))
                    {
                        foreach ($value as $key2 => $value2)
                        {
                            $decrypted_array[$key][$key2] = $this->decrypt_data($value2);
                        }
                    }
                    else
                    {
                        $decrypted_array[$key] = $this->decrypt_data($value);
                    }
                }
                
                return $decrypted_array;
            }
            else if (!is_null($data) &&
                     !is_null($init_vector))
            {
                return $this->decrypt_data($data, $init_vector);
            }
            else if ($this->is_json($data) && is_null($init_vector))
            {
                return $this->decrypt_data($data);
            }
        }
        catch (Exception $exception)
        {
            echo $exception->getMessage();
        }
    }
}

$aes = new AES;
$aes->master_key('e16cd89767a18c53');

$data = array(
         array('name' => 'destramic',
               'age' => '28'),
         array('name' => 'alan',
               'age' => '99')    
);

$encryption = $aes->encrypt($data);

print_r($encryption);
print_r($aes->decrypt($encryption));

works a charm now and thank you for all your help

 

could i asks just few thing if i may please...would using a 256 bit ecryption be a over kill?

 

also i'm thinking of just encrypting eventhing in my database or is it just worth only encrypting sensitive data like address' passwords etc?

 

oh and i read that storing the master key on a seperate server is the best way...if like me you dont have a sepeate server is a .ini file suffice...if so should i zip it?

 

 

thank you

Link to comment
Share on other sites

ok will you help i reviewed the way i was doing things as i wanted to use PKCS#7 instead of OPENSSL_RAW_DATA as i read it is the best method?

 

OPENSSL_RAW_DATA has nothing to do with padding. It only tells the function to use binary strings rather than Base64-encoded strings (which, for some reason, is the default).

 

I think you're confusing OPENSSL_RAW_DATA with OPENSSL_ZERO_PADDING,.

 

 

 

could i asks just few thing if i may please...would using a 256 bit ecryption be a over kill?

 

It's already impossible to crack AES-128, so increasing the key size provides no benefit. It's just slower.

 

 

 

also i'm thinking of just encrypting eventhing in my database or is it just worth only encrypting sensitive data like address' passwords etc?

 

Encrypting all data will be extremely complicated and doesn't improve security, so, no, I wouldn't do it.

 

Also note that the passwords still need to be hashed with a strong algorithm like bcrypt. Encryption is just an additional security layer.

 

 

 

oh and i read that storing the master key on a seperate server is the best way...if like me you dont have a sepeate server is a .ini file suffice...if so should i zip it?

 

Storing the key in a file outside of the document root and make it readable only for the webserver is pretty much the best you can do in your case.

 

In professional environments, keys are stored on specialized hardware security modules where they cannot be accessed directly, but this is far too expensive for a normal website.

 

 

 

Regarding the code:

  • Your keys are now far, far too short, because you're still confusing bytes and hex strings. An AES-128 key must be exactly 128 bits (or 16 bytes long). The hex representation is twice as long, i. e. 32 characters. Do not use 16 hex characters, because that's only 64 bits and can actually be cracked. You should store the key length in bytes in a class constant and always use that constant when generating keys. Remove all those length parameters, because they're useless and potentially dangerous.
  • You need a lot more validation, and your error handling should be much stricter. Validate everything (input data, return values etc.), and if there's any problem, throw an exception or trigger a fatal error. Don't rely on return values, because if the caller doesn't check them, the error may go unnoticed.
  • You should simplify your class and remove everything which isn't directly related to cryptographic operations. For example, get rid of the JSON stuff and the if-then-else magic covering dozens of special cases. The class should only do two things: Encrypt a plaintext string and decrypt a ciphertext string. Everything else belongs into a separate class. Avoid complex classes, because they dramatically increase the risk of errors.
  •  
Link to comment
Share on other sites

  • 2 weeks later...

sorry for the delay but i haven't been about to reply....but thank you for the great information and helping me to understand aes a lot more than i did, it's greatly appreciated...i'll take on board your advice and sort my coding out, once done i'll post my code for others to see and learn

 

cheers jacques :happy-04:

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.