Skip to content

Instantly share code, notes, and snippets.

@cromefire
Created August 1, 2019 12:25
Show Gist options
  • Save cromefire/02f89c62829d601264747dcee82f424d to your computer and use it in GitHub Desktop.
Save cromefire/02f89c62829d601264747dcee82f424d to your computer and use it in GitHub Desktop.
ECIES like/alternative for Python & TypeScript
// DISCLAIMER: I cannot guarantee that this is secure
import { Encryptor } from "./encryptor";
/**
* Generates a shared secret and a public key, private key immediatly discarded
*/
async function derive(
remotePub: ArrayBuffer
): Promise<{ key: Uint8Array; pub: Uint8Array; salt: Uint8Array }> {
const targetAlgo = {
length: 256,
name: "AES-GCM"
};
const algo = {
name: "ECDH",
namedCurve: "P-521"
};
// public key of other party
let publicCrypto: CryptoKey = await crypto.subtle.importKey(
"raw",
remotePub,
algo,
false,
[]
);
const algoWPub = Object.assign(algo, {
public: publicCrypto
});
// Browser keypair
const ownKeys = await window.crypto.subtle.generateKey(algo, false, [
"deriveKey"
]);
// ECDH
const derivedCrypto = await window.crypto.subtle.deriveKey(
algoWPub,
ownKeys.privateKey,
targetAlgo as any,
true,
["encrypt"] // Chrome bugs all the way
);
// ECDH key
const derived = new Uint8Array(
await crypto.subtle.exportKey("raw", derivedCrypto)
);
// Public key
const pub = new Uint8Array(
await crypto.subtle.exportKey("raw", ownKeys.publicKey)
);
// Generate salt
const salt = new Uint8Array(12);
crypto.getRandomValues(salt);
// Derive key with scrypt; not included in this gist (bring you own implementation, mine is to specific)
const hashed = await scrypt(
{
password: Array.from(derived),
salt: Array.from(salt),
dkLen: 32,
n: 2 ** 8,
r: 32,
p: 1
}
);
return {
key: hashed,
pub,
salt
};
}
/**
* Creates something that can encrypt multiple values from the othe party's public key
*/
export async function getEncryptor(remotePub: ArrayBuffer): Promise<Encryptor> {
const pubAndKey = await derive(remotePub);
return new Encryptor(pubAndKey.key, pubAndKey.pub, pubAndKey.salt);
}
# DISCLAIMER: I cannot guarantee that this is secure
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.ec import (
EllipticCurvePrivateKey,
ECDH,
EllipticCurvePublicKey,
EllipticCurvePublicNumbers,
SECP521R1
)
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from miscreant.aes.siv import SIV
from miscreant.exceptions import IntegrityError
SALT = b"<some additional static salt value>" # Same as in browser
def decrypt(private_key: EllipticCurvePrivateKey, public_point: bytes, salt: bytes, iv: bytes) -> bytes:
"""
Decrypt value (encryption similar and probably shares most of the code)
:param key: Private key for decryption
:param public_point: Public key of the other party
:param salt: Salt for scrypt of the other party
:param iv: IV (In AES-RIV also adata in one) of the other party
:return: decrypted value
:raises AttributeError: If some value is "" (I think?)
:raises IntegrityError: If some data is corrupt/invalid/you are being attacked
:raises ValueError: If the salt or public_point is empty
"""
if public_point == b"":
raise ValueError("public_point invalid")
if salt == b"":
raise ValueError("salt invalid")
# SECP521R1: because I'm constrained what browsers can
numbers = EllipticCurvePublicNumbers.from_encoded_point(SECP521R1(), public_point)
pub: EllipticCurvePublicKey = numbers.public_key(default_backend())
ecdh_key: bytes = key.exchange(ECDH(), pub)[0:32] # Only half of the key because, browsers only give the first half
shared_key: bytes = Scrypt(salt, 32, 2 ** 8, 32, 1, default_backend()).derive(ecdh_key)
del ecdh_key # Keep everyting clean (There might be a better way to fully delete it from memory)
cipher = SIV(shared_key)
# decryption, you might also use this The other way around in that case generate the IV (and a keypair if not already there)
# and send public key and IV to the other party
decrypted: bytes = cipher.open(ciphertext, [SALT, iv])
return decrypted
// DISCLAIMER: I cannot guarantee that this is secure
import { SIV } from "miscreant";
const SALT = new TextEncoder().encode("<some additional static salt value>"); // Same as in Python
export interface IEncrypted {
cipherText: Uint8Array;
iv: Uint8Array;
}
/**
* Can only encrypt, but can easiely be converted to something that can also decrypt
*/
export class Encryptor {
public readonly pub: Uint8Array;
public readonly salt: Uint8Array;
private readonly key: Uint8Array;
private encoder = new TextEncoder();
constructor(key: Uint8Array, pub: Uint8Array, salt: Uint8Array) {
this.key = key;
this.pub = pub;
this.salt = salt;
}
private async encrypt(plaintext: Uint8Array): Promise<IEncrypted> {
const iv = new Uint8Array(32);
crypto.getRandomValues(iv);
const cryptoKey = await SIV.importKey(this.key, "AES-SIV");
const ciphertext = await cryptoKey.seal(plaintext, [SALT, iv]);
const cipherText = new Uint8Array(ciphertext);
return {
cipherText,
iv
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment