Skip to content

Instantly share code, notes, and snippets.

@marcoslin
Last active August 31, 2023 15:37
  • Star 32 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save marcoslin/8026990 to your computer and use it in GitHub Desktop.
Encryption: From PyCrypto to CryptoJS

Encryption: From PyCrypto to CryptoJS

Sample code to encrypt using Python and decrypt in Javascript

var encrypted;
// Send the #to_encrypt string to be encrypted by Python and populate the Encryption Result Section
$("#do_encrypt").click(function () {
var post_data = {
to_encrypt: $("#to_encrypt").val()
};
$.post("/encrypt", post_data, function (data) {
encrypted = data;
$("#encrypt_key").val(data.key);
$("#encrypt_iv").val(data.iv);
$("#encrypt_ct").val(data.ciphertext);
$("#do_decrypt").removeAttr("disabled");
});
});
// Output the decryption result to #decrypted, based on encrypted data from #do_encrypt
$("#do_decrypt").click(function () {
var key = CryptoJS.enc.Hex.parse(encrypted.key),
iv = CryptoJS.enc.Hex.parse(encrypted.iv),
cipher = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(encrypted.ciphertext)
}),
result = CryptoJS.AES.decrypt(cipher, key, {iv: iv, mode: CryptoJS.mode.CFB});
$('#decrypted').val(result.toString(CryptoJS.enc.Utf8));
});
<html>
<head>
<title>Encryption: from Python to JS</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" type="text/css" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<div class="container">
<h1>Encryption: from Python to JS</h1>
<!-- To Encrypt Section -->
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="to_encrypt">To encrypt:</label>
<textarea id="to_encrypt" class="form-control">A very secret statement made by an import person.</textarea>
</div>
<button id="do_encrypt" type="button" class="btn btn-primary">Encrypt Using Python</button>
</div>
</div>
<!-- Encrypted Result Section -->
<div class="panel panel-default">
<div class="panel-body">
<label>Encryption Result:</label>
<div class="form-horizontal">
<div class="form-group">
<label for="encrypt_key" class="col-sm-2 control-label">Key:</label>
<div class="col-sm-10">
<input id="encrypt_key" class="form-control" disabled>
</div>
</div>
<div class="form-group">
<label for="encrypt_iv" class="col-sm-2 control-label">IV:</label>
<div class="col-sm-10">
<input id="encrypt_iv" class="form-control" disabled>
</div>
</div>
<div class="form-group">
<label for="encrypt_ct" class="col-sm-2 control-label">Encrypted:</label>
<div class="col-sm-10">
<textarea id="encrypt_ct" class="form-control" disabled></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- Decrypt Section -->
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="decrypted">Decrypt:</label>
<textarea id="decrypted" class="form-control"></textarea>
</div>
<button id="do_decrypt" type="button" class="btn btn-primary" disabled>Decrypt Using JS</button>
</div>
</div>
</div>
<script src="//crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script src="//crypto-js.googlecode.com/svn/tags/3.1.2/build/components/mode-cfb.js"></script>
<script src="app.js"></script>
</body>
</html>
#!/usr/bin/env python
import os, json
import binascii
from cgi import parse_header, parse_multipart
from urlparse import parse_qs
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from Crypto import Random
from Crypto.Cipher import AES
# ------------------------------
# DEFINE Encryption Class
class Cryptor(object):
'''
Provide encryption and decryption function that works with crypto-js.
https://code.google.com/p/crypto-js/
Padding implemented as per RFC 2315: PKCS#7 page 21
http://www.ietf.org/rfc/rfc2315.txt
The key to make pycrypto work with crypto-js are:
1. Use MODE_CFB. For some reason, crypto-js decrypted result from MODE_CBC
gets truncated
2. Use Pkcs7 padding as per RFC 2315, the default padding used by CryptoJS
3. On the JS side, make sure to wrap ciphertext with CryptoJS.lib.CipherParams.create()
'''
# AES-256 key (32 bytes)
KEY = "01ab38d5e05c92aa098921d9d4626107133c7e2ab0e4849558921ebcc242bcb0"
BLOCK_SIZE = 16
@classmethod
def _pad_string(cls, in_string):
'''Pad an input string according to PKCS#7'''
in_len = len(in_string)
pad_size = cls.BLOCK_SIZE - (in_len % cls.BLOCK_SIZE)
return in_string.ljust(in_len + pad_size, chr(pad_size))
@classmethod
def _unpad_string(cls, in_string):
'''Remove the PKCS#7 padding from a text string'''
in_len = len(in_string)
pad_size = ord(in_string[-1])
if pad_size > cls.BLOCK_SIZE:
raise ValueError('Input is not padded or padding is corrupt')
return in_string[:in_len - pad_size]
@classmethod
def generate_iv(cls, size=16):
return Random.get_random_bytes(size)
@classmethod
def encrypt(cls, in_string, in_key, in_iv=None):
'''
Return encrypted string.
@in_string: Simple str to be encrypted
@key: hexified key
@iv: hexified iv
'''
key = binascii.a2b_hex(in_key)
if in_iv is None:
iv = cls.generate_iv()
in_iv = binascii.b2a_hex(iv)
else:
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
return in_iv, aes.encrypt(cls._pad_string(in_string))
@classmethod
def decrypt(cls, in_encrypted, in_key, in_iv):
'''
Return encrypted string.
@in_encrypted: Base64 encoded
@key: hexified key
@iv: hexified iv
'''
key = binascii.a2b_hex(in_key)
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
decrypted = aes.decrypt(binascii.a2b_base64(in_encrypted).rstrip())
return cls._unpad_string(decrypted)
# ------------------------------
# DEFINE HTTP Handler
class EncryptHandler(BaseHTTPRequestHandler):
'''
Simple webserver that server index.html and static "*.html" and "*.js" files,
which special "/encrypt" URL that would return a JSON with encrypted data
'''
PORT_NUMBER = 8087
SCRIPT_PATH = os.path.dirname(__file__)
def _return_http_code(self, http_code):
self.send_response(http_code)
self.end_headers()
def _return_file(self, in_file):
''' '''
if os.path.exists(in_file):
with open(in_file) as f:
self.send_response(200)
if in_file.endswith(".html"):
self.send_header('Content-type',"text/html")
elif in_file.endswith(".js"):
self.send_header('Content-type',"text/javascript")
else:
self.send_header('Content-type',"text/plain")
self.end_headers()
self.wfile.write(f.read())
else:
self._return_http_code(404)
def _return_json(self, in_dict):
'''Send JSON back to client from a dictionary'''
self.send_response(200)
self.send_header('Content-type','application/json')
self.end_headers()
self.wfile.write(json.dumps(in_dict))
def do_GET(self):
'''
Serve static .html and .js files
'''
request_path = self.path
if request_path == "/":
self._return_file("index.html")
elif request_path.endswith(".html") or request_path.endswith(".js"):
self._return_file(self.SCRIPT_PATH + request_path)
else:
self._return_http_code(404)
return
def parse_POST(self):
'''
parse POST body
'''
ctype, pdict = parse_header(self.headers.getheader('content-type'))
if ctype == 'multipart/form-data':
postvars = parse_multipart(self.rfile, pdict)
elif ctype == 'application/x-www-form-urlencoded':
length = int(self.headers.getheader('content-length'))
postvars = parse_qs(self.rfile.read(length), keep_blank_values=1)
else:
postvars = {}
return postvars
def do_POST(self):
'''
Perform the encryption in Python
'''
if self.path == "/encrypt":
postvars = self.parse_POST()
if postvars:
to_encrypt = ''.join(postvars["to_encrypt"])
iv, encrypted = Cryptor.encrypt(to_encrypt, Cryptor.KEY)
result = {
"key": Cryptor.KEY,
"iv": iv,
"ciphertext": binascii.b2a_base64(encrypted).rstrip()
}
#print "Encrypt result: %s" % result
self._return_json(result)
else:
print "Error: POST received no data."
self._return_http_code(500)
else:
self._return_http_code(404)
return
# ------------------------------
# START WEB SERVER
try:
#Create a web server and define the handler to manage the
#incoming request
server = HTTPServer(('', EncryptHandler.PORT_NUMBER), EncryptHandler)
print 'Started httpserver on port ' , EncryptHandler.PORT_NUMBER
#Wait forever for incoming htto requests
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down the web server'
server.socket.close()
@pydevd
Copy link

pydevd commented Jul 12, 2014

IT WORKS!!!
Thank you so much!!! I've spent a day figuring out why CryptoJS don't want to decrypt data from PyCrypto!!!

@jmrnilsson
Copy link

ah, nice +1

@armandomiani
Copy link

Amazing!

@sergzach
Copy link

sergzach commented Mar 2, 2016

Thank you.

@kodeine
Copy link

kodeine commented Nov 26, 2016

So i've been using this, it works good 99% of the times. But 1% of those times i get,

 Input strings must be a multiple of the segment size 16 in length

To test, this is the base64,

xn4g7HpQNZJcOzm+h4AEkUkaXF/FkaWE6HvsYQH53dYmSHb1oa6t2MmVBVCIXOC/3AbFuQ9HbuO5BYxVGovqNMeY9P2PnXGMqYayUQpP80ZfYORVEgKYtDh+KkBcQ2PrwkknGBeULGPQGMOuS+97MmBFxa0pkZM/DqUg73gEgqHVHjMb22//TDkXvruWxy3MDhWwk+MmFaagDZqcKPRTNOiCAesSaoB4KzwzqIuPuAUF8l0HASFcmbeU8SKwmbMg

IV

8a609bf8eeb96d339ecedf59aed5b43b

Cryptor.KEY remains the same.

Try to decrypt this in python you'll get the mentioned error, but it decrypts fine in cryptoJS.

Any help would be really appreciated.

Thanks

@yashraj754
Copy link

Not working. How to use?

@owlstead
Copy link

owlstead commented Jan 5, 2019

Sending any kind of data over the internet without authentication is bunk. CFB doesn't even require padding, so why do you add it? Possibly, if CBC is not working, you might want and try to find out why instead of randomly using another mode of operation.

Simple, yay! "it works", yay! Just a shame it is completely insecure to send anything over the wire using just CFB mode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment