Created
August 1, 2019 12:25
-
-
Save cromefire/02f89c62829d601264747dcee82f424d to your computer and use it in GitHub Desktop.
ECIES like/alternative for Python & TypeScript
This file contains 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
// 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); | |
} |
This file contains 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
# 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 |
This file contains 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
// 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