HighSide Open Sources its Encryption Code

Here we open source the encryption modules used in the ClearChat client. We share it under the MIT license in the hope that it will be of use to the public.

AESEncryption.py does authenticated AES256 encryption and decryption.
highlevelcrypto.py does public key operations like encryption and signing.
example.py shows how to use them. It runs under Python 2.7 and there are two prerequisites: pyelliptic and OpenSSL.

AESEncryption.py

import os
import hashlib
import hmac
import pyelliptic

def AES_CTRWithHMACEncrypt(keySeedMaterial, data):
    assert len(keySeedMaterial) == 32
    keys = hashlib.sha512(hashlib.sha512(keySeedMaterial).digest()).digest()
    HMACKey = keys[:32]
    encryptionKey = keys[32:]
    iv = pyelliptic.Cipher.gen_IV('aes-256-ctr')
    ctx = pyelliptic.Cipher(encryptionKey, iv, 1, ciphername='aes-256-ctr')
    
    ciphertext = ctx.update(data)
    ciphertext += ctx.final()
    hmacDigest = hmac.new(HMACKey, iv + ciphertext, hashlib.sha512).digest()
    return hmacDigest + iv + ciphertext

def AES_CTRWithHMACDecrypt(keySeedMaterial, data):
    assert len(keySeedMaterial) == 32
    keys = hashlib.sha512(hashlib.sha512(keySeedMaterial).digest()).digest()
    HMACKey = keys[:32]
    encryptionKey = keys[32:]
    includedHMACDigest = data[:64]
    iv = data[64:80]
    ciphertext = data[80:]
    calculatedHMACDigest = hmac.new(HMACKey, iv + ciphertext, hashlib.sha512).digest()
    if not hmac.compare_digest(includedHMACDigest, calculatedHMACDigest): # constant time comparison
        raise Exception("HMAC failed. Cannot decrypt data. The data has been modified.")
    ctx2 = pyelliptic.Cipher(encryptionKey, iv, 0, ciphername='aes-256-ctr')
    return ctx2.ciphering(ciphertext)

highlevelcrypto.py

import pyelliptic
from pyelliptic import OpenSSL
def makeCryptor(private_key):
    public_key = pointMult(private_key)
    privkey_bin = '\x02\xca\x00\x20' + private_key
    pubkey_bin = '\x02\xca\x00\x20' + public_key[1:33] + '\x00\x20' + public_key[33:]
    cryptor = pyelliptic.ECC(curve='secp256k1',privkey=privkey_bin,pubkey=pubkey_bin)
    return cryptor
def addOpenSSLKeyFormatting(pubkey):
    return '\x02\xca\x00\x20'+pubkey[1:33]+'\x00\x20'+pubkey[33:]
def makePubCryptor(pubkey):
    pubkey = addOpenSSLKeyFormatting(pubkey)
    return pyelliptic.ECC(curve='secp256k1',pubkey=pubkey)
# Converts private key into public key
def privToPub(private_key):
    return pointMult(private_key)
def encrypt(msg,pubkey):
    return pyelliptic.ECC(curve='secp256k1').encrypt(msg,addOpenSSLKeyFormatting(pubkey))
def decrypt(msg,privkey):
    return makeCryptor(privkey).decrypt(msg)
def sign(msg,privKey):
    return makeCryptor(privKey).sign(msg)
def verify(msg,sig,pubkey): 
    try:
        return makePubCryptor(pubkey).verify(sig,msg)
    except:
        return False

# Does an EC point multiplication; turns a private key into a public key.
def pointMult(secret):
    while True:
        try:
            k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1'))
            priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
            group = OpenSSL.EC_KEY_get0_group(k)
            pub_key = OpenSSL.EC_POINT_new(group)
            
            OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None)
            OpenSSL.EC_KEY_set_private_key(k, priv_key)
            OpenSSL.EC_KEY_set_public_key(k, pub_key)
            
            size = OpenSSL.i2o_ECPublicKey(k, None)
            mb = OpenSSL.create_string_buffer(size)
            OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb)))
            
            OpenSSL.EC_POINT_free(pub_key)
            OpenSSL.BN_free(priv_key)
            OpenSSL.EC_KEY_free(k)
            return mb.raw

        except Exception as e:
            """
            Evidently, this type of error can occur very rarely. If we just try again it will work.
            
            File "highlevelcrypto.py", line 35, in pointMult
              group = OpenSSL.EC_KEY_get0_group(k)
            WindowsError: exception: access violation reading 0x0000000000000008
            """            
            import traceback
            import time
            traceback.print_exc()
            time.sleep(0.2)

example.py

import os
import sys
import struct
from AESEncryption import AES_CTRWithHMACEncrypt, AES_CTRWithHMACDecrypt
import highlevelcrypto

# First let's try a simple symmetric encryption operation.
key = os.urandom(32)
message = """And Saint Attila raised the hand grenade up on high, saying, "O 
Lord, bless this Thy hand grenade that, with it, Thou mayest blow Thine enemies 
to tiny bits in Thy mercy." And the Lord did grin, and the people did feast 
upon the lambs and sloths and carp and anchovies and orangutans and breakfast 
cereals and fruit bats and large chu--\"\n"""

encryptedData = AES_CTRWithHMACEncrypt(key, message)
decryptedData = AES_CTRWithHMACDecrypt(key, encryptedData)

print "Decrypted data:", decryptedData

# Very simple! Now let's try asymetric encryption. Alice and Bob each generate 
# a private and public key for themselves.

alicePrivateKey = os.urandom(32)
bobPrivateKey = os.urandom(32)

alicePublicKey = highlevelcrypto.privToPub(alicePrivateKey)
bobPublicKey = highlevelcrypto.privToPub(bobPrivateKey)

# Alice and Bob acquire each other's public keys.

# Suppose Alice wants to send a message to Bob.

messageSender = "Alice"
message = """And the Lord spake, saying, "First shalt thou take out the Holy 
Pin. Then, shalt thou count to three. No more. No less. Three shalt be the 
number thou shalt count, and the number of the counting shall be three. Four 
shalt thou not count, nor either count thou two, excepting that thou then 
proceed to three. Five is right out. Once the number three, being the third 
number, be reached, then, lobbest thou thy Holy Hand Grenade of Antioch towards 
thy foe, who, being naughty in My sight, shall snuff it."
Amen"""

# In order to prevent forwarding attacks, it is important to include the 
# message sender in the encrypted message so that Bob can verify it later.
message = struct.pack('>L', len(messageSender)) + messageSender + message

encryptedMessage = highlevelcrypto.encrypt(message, bobPublicKey)
signature = highlevelcrypto.sign(encryptedMessage, alicePrivateKey) 

# Alice sends Bob the encrypted message and the signature. 

if highlevelcrypto.verify(encryptedMessage, signature, alicePublicKey):
    print "Signature verification passed."
else:
    print "Signature verification failed!"
    sys.exit(1)

# Bob is now sure that this message came from Alice. 

decryptedData = highlevelcrypto.decrypt(encryptedMessage, bobPrivateKey)
lengthOfWritersName = struct.unpack('>L', decryptedData[:4])[0]
writersName = decryptedData[4:4+lengthOfWritersName]
decryptedMessage = decryptedData[4+lengthOfWritersName:]

# Bob must verify that the sender of this message matches the person who 
# apparently sent it and signed it.
if writersName != "Alice":
    print "Sender is not who I expected! Ignoring message completely."
    # Do not feel tempted to show the message. Always block messages when 
    # security checks fail.  
    sys.exit(1)

# Bob is now sure that this message came from Alice *and* that Alice wrote it.

print "Message was written by", writersName
print "Decrypted message:", decryptedMessage

Leave a Reply

Your email address will not be published. Required fields are marked *