Skip to content

Commit 2bbc8cf

Browse files
committed
Splitting into languages
1 parent 7ac309d commit 2bbc8cf

9 files changed

Lines changed: 314 additions & 0 deletions

File tree

File renamed without changes.

nodejs/decrypt2.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
var base64 = require('urlsafe-base64');
4+
var crypto = require('crypto');
5+
var ece = require('./ece.js');
6+
7+
if (process.argv.length < 5) {
8+
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9+
' <key> <salt> <message>');
10+
process.exit(2);
11+
}
12+
13+
var result = ece.decrypt(base64.decode(process.argv[4]), {
14+
key: process.argv[2],
15+
salt: process.argv[3]
16+
});
17+
18+
console.log(base64.encode(result));
19+
console.log(result.toString('utf-8'));
File renamed without changes.

nodejs/encrypt2.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
var base64 = require('urlsafe-base64');
4+
var crypto = require('crypto');
5+
var ece = require('./ece.js');
6+
7+
if (process.argv.length < 5) {
8+
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9+
' <key> <salt> <message>');
10+
process.exit(2);
11+
}
12+
13+
var result = ece.encrypt(base64.decode(process.argv[4]), {
14+
key: process.argv[2],
15+
salt: process.argv[3]
16+
});
17+
18+
console.log(base64.encode(result));
19+
console.log(result.toString('utf-8'));

nodejs/gcm.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
var base64 = require('urlsafe-base64');
2+
var crypto = require('crypto');
3+
4+
var buffer = base64.decode('HH5QdMESn_mjg-BDyY6xrURjmeHmlesP3l7HopOOMAhtcWkH7KNKlxLlkBoligipE3pr6hoCbzdv1IOBErrrphIWIZIkyt7WZybg4o0PudVQaFoL82x2MpashMq2lqmVM6HY_GdSYyANzpeiXA7T6EoicC1Y');
5+
var gcm = crypto.createDecipheriv('id-aes128-GCM', base64.decode('HUV2cCEaSNAX0FWaZgMlzA'), base64.decode('YJ8P9Oy8k5J7TWoW'));
6+
gcm.setAuthTag(buffer.slice(buffer.length - 16));
7+
var data = Buffer.concat([gcm.update(buffer.slice(0, buffer.length - 16)), gcm.final()]);
8+
console.log(base64.encode(data.slice(1)));
File renamed without changes.
File renamed without changes.

python/ece.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import os, struct
2+
from cryptography.hazmat.primitives import hashes
3+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
4+
from cryptography.hazmat.backends import default_backend
5+
from cryptography.hazmat.primitives.ciphers import (
6+
Cipher, algorithms, modes
7+
)
8+
import pyelliptic
9+
10+
keys = {}
11+
12+
def deriveKey(salt, key=None, dh=None, keyid=None):
13+
if salt is None or len(salt) != 16:
14+
raise Exception(u"'salt' must be a 16 octet value")
15+
16+
if key is not None:
17+
secret = key
18+
elif dh is not None:
19+
if keyid is None:
20+
raise Exception(u"'keyid' is not specified with 'dh'")
21+
if keys[keyid] is None:
22+
raise Exception(u"'keyid' doesn't identify a key")
23+
secret = keys[keyid].get_ecdh_key(dh)
24+
elif keyid is not None:
25+
secret = keys[keyid]
26+
if secret is None:
27+
raise Exception(u"unable to determine the secret")
28+
29+
hkdf_key = HKDF(
30+
algorithm=hashes.SHA256(),
31+
length=16,
32+
salt=salt,
33+
info=b"Content-Encoding: aesgcm128",
34+
backend=default_backend()
35+
)
36+
hkdf_nonce = HKDF(
37+
algorithm=hashes.SHA256(),
38+
length=12,
39+
salt=salt,
40+
info=b"Content-Encoding: nonce",
41+
backend=default_backend()
42+
)
43+
return (hkdf_key.derive(secret), hkdf_nonce.derive(secret))
44+
45+
def iv(base, counter):
46+
if (counter >> 64) != 0:
47+
raise Exception(u"Counter too big")
48+
(mask,) = struct.unpack("!Q", base[4:])
49+
return base[:4] + struct.pack("!Q", counter ^ mask)
50+
51+
def decrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096):
52+
def decryptRecord(key, nonce, counter, buffer):
53+
decryptor = Cipher(
54+
algorithms.AES(key),
55+
modes.GCM(iv(nonce, counter), tag=buffer[-16:]),
56+
backend=default_backend()
57+
).decryptor()
58+
data = decryptor.update(buffer[:-16]) + decryptor.finalize()
59+
(pad,) = struct.unpack("!B", data[0]);
60+
if data[1:1+pad] != (b"\x00" * pad):
61+
raise Exception(u"Bad padding")
62+
data = data[1+pad:]
63+
return data
64+
65+
(key_, nonce_) = deriveKey(salt=salt, key=key, keyid=keyid, dh=dh)
66+
if rs < 2:
67+
raise Exception(u"Record size too small")
68+
rs += 16 # account for tags
69+
if len(buffer) % rs == 0:
70+
raise Exception(u"Message truncated")
71+
72+
result = b""
73+
counter = 0
74+
for i in xrange(0, len(buffer), rs):
75+
result += decryptRecord(key_, nonce_, counter, buffer[i:i+rs])
76+
++counter
77+
return result
78+
79+
def encrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096):
80+
def encryptRecord(key, nonce, counter, buffer):
81+
encryptor = Cipher(
82+
algorithms.AES(key),
83+
modes.GCM(iv(nonce, counter)),
84+
backend=default_backend()
85+
).encryptor()
86+
data = encryptor.update(b"\x00" + buffer) + encryptor.finalize()
87+
data += encryptor.tag
88+
return data
89+
90+
(key_, nonce_) = deriveKey(salt=salt, key=key, keyid=keyid, dh=dh)
91+
if rs < 2:
92+
raise Exception(u"Record size too small")
93+
rs -= 1 # account for padding
94+
95+
result = b""
96+
counter = 0
97+
# the extra one ensures that we produce a padding only record if the data
98+
# length is an exact multiple of rs-1
99+
for i in xrange(0, len(buffer) + 1, rs):
100+
result += encryptRecord(key_, nonce_, counter, buffer[i:i+rs])
101+
++counter
102+
return result
103+
104+
if __name__ == "__main__":
105+
import base64
106+
107+
salt=base64.urlsafe_b64decode("mUFsKgrmI-i_-HowjX_2XA==")
108+
key=base64.urlsafe_b64decode("F-hAEGCm7KIGUiSdS4GGtA==")
109+
m = base64.urlsafe_b64decode("iEPbDBuohQLznv45IlaF1eLRCeu6aWfsq-pDP7OnzgH4A0x5lyIEVAfM39RgeLekW1VgZWIFL_WvuveEhaHj0-iEvxDHw_apYGFYWEY6KmMhXgWPmFZ-2wAMnDsQ-DDVbZHsXw==")
110+
rs=3300
111+
print ("message", len(m), base64.urlsafe_b64encode(m))
112+
e = encrypt(m, salt=salt, key=key, rs=rs)
113+
print ("encrypted", len(e), base64.urlsafe_b64encode(e))
114+
d = decrypt(e, salt=salt, key=key, rs=rs)
115+
print ("decrypted", len(d), base64.urlsafe_b64encode(d))
116+
print (m == d)
117+
118+
salt = os.urandom(16)
119+
print ("salt", base64.urlsafe_b64encode(salt))
120+
keys["receiver"] = pyelliptic.ECC(curve="prime256v1")
121+
print ("receiver", base64.urlsafe_b64encode(keys["receiver"].get_pubkey()),
122+
base64.urlsafe_b64encode(keys["receiver"].get_privkey()))
123+
keys["sender"] = pyelliptic.ECC(curve="prime256v1")
124+
print ("sender", base64.urlsafe_b64encode(keys["sender"].get_pubkey()),
125+
base64.urlsafe_b64encode(keys["sender"].get_privkey()))
126+
127+
e = encrypt(m, salt=salt, keyid="sender", dh=keys["receiver"].get_pubkey(), rs=rs)
128+
print ("encrypted", len(e), base64.urlsafe_b64encode(e))
129+
d = decrypt(e, salt=salt, keyid="receiver", dh=keys["sender"].get_pubkey(), rs=rs)
130+
print ("decrypted", len(d), base64.urlsafe_b64encode(d))
131+
print (m == d)

python/test.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import ece
2+
import base64
3+
import os
4+
import struct
5+
import sys
6+
import pyelliptic
7+
8+
count = 20
9+
if len(sys.argv) > 1:
10+
count = int(sys.argv[1])
11+
maxLen = 100
12+
if len(sys.argv) > 2:
13+
maxLen = int(sys.argv[2])
14+
15+
def log(arg):
16+
if (count == 1):
17+
print arg
18+
19+
def rlen():
20+
return struct.unpack_from('=H', os.urandom(2))[0]
21+
22+
def encryptDecrypt(length, encryptParams, decryptParams=None):
23+
if decryptParams is None:
24+
decryptParams = encryptParams
25+
log('Salt: ' + base64.urlsafe_b64encode(encryptParams['salt']))
26+
input = os.urandom(min(length, maxLen))
27+
# input = new Buffer('I am the walrus')
28+
log('Input: ' + base64.urlsafe_b64encode(input))
29+
encrypted = ece.encrypt(input, salt=encryptParams.get('salt'), key=encryptParams.get('key'), keyid=encryptParams.get('keyid'), dh=encryptParams.get('dh'), rs=encryptParams.get('rs'))
30+
log('Encrypted: ' + base64.urlsafe_b64encode(encrypted))
31+
decrypted = ece.decrypt(encrypted, salt=decryptParams.get('salt'), key=decryptParams.get('key'), keyid=decryptParams.get('keyid'), dh=decryptParams.get('dh'), rs=decryptParams.get('rs'))
32+
log('Decrypted: ' + base64.urlsafe_b64encode(decrypted))
33+
assert input == decrypted
34+
log("----- OK");
35+
36+
37+
def useExplicitKey():
38+
params = {
39+
'key': os.urandom(16),
40+
'salt': os.urandom(16),
41+
'rs': rlen() + 1
42+
}
43+
log('Key: ' + base64.urlsafe_b64encode(params['key']))
44+
encryptDecrypt(rlen() + 1, params)
45+
46+
47+
def exactlyOneRecord():
48+
length = min(rlen(), maxLen)
49+
params = {
50+
'key': os.urandom(16),
51+
'salt': os.urandom(16),
52+
'rs': length + 1
53+
}
54+
encryptDecrypt(length, params)
55+
56+
57+
def detectTruncation():
58+
length = min(rlen(), maxLen)
59+
key = os.urandom(16)
60+
salt = os.urandom(16)
61+
rs = length + 1
62+
input = os.urandom(min(length, maxLen))
63+
encrypted = ece.encrypt(input, salt=salt, key=key, rs=rs)
64+
ok = False
65+
try:
66+
ece.decrypt(encrypted[0:length + 1 + 16], salt=salt, key=key, rs=rs)
67+
except Exception as e:
68+
log('Decryption error: %s' % e.args)
69+
log('----- OK')
70+
ok = True
71+
72+
if not ok:
73+
raise Exception('Decryption succeeded, but should not have')
74+
75+
76+
77+
def useKeyId():
78+
keyid = base64.urlsafe_b64encode(os.urandom(16))
79+
key = os.urandom(16)
80+
ece.keys[keyid] = key
81+
params = {
82+
'keyid': keyid,
83+
'salt': os.urandom(16),
84+
'rs': rlen() + 1
85+
}
86+
encryptDecrypt(rlen(), params)
87+
88+
89+
# This is a complete crap-shoot: the pre-eminent crypto library in python
90+
# doesn't even do ECDH; so this doesn't actually work
91+
def useDH():
92+
def isUncompressed(k):
93+
b1 = k.get_pubkey()[0]
94+
assert struct.unpack("B", b1)[0] == 4, 'is an uncompressed point'
95+
96+
# the static key is used by the receiver
97+
staticKey = pyelliptic.ECC(curve='prime256v1')
98+
isUncompressed(staticKey)
99+
staticKeyId = base64.urlsafe_b64encode(staticKey.get_pubkey()[1:])
100+
ece.keys[staticKeyId] = staticKey
101+
102+
log('Receiver private: ' + base64.urlsafe_b64encode(staticKey.get_privkey()))
103+
log('Receiver public: ' + base64.urlsafe_b64encode(staticKey.get_pubkey()))
104+
105+
# the ephemeral key is used by the sender
106+
ephemeralKey = pyelliptic.ECC(curve='prime256v1')
107+
isUncompressed(ephemeralKey)
108+
ephemeralKeyId = base64.urlsafe_b64encode(ephemeralKey.get_pubkey()[1:])
109+
ece.keys[ephemeralKeyId] = ephemeralKey
110+
111+
log('Sender private: ' + base64.urlsafe_b64encode(ephemeralKey.get_privkey()))
112+
log('Sender public: ' + base64.urlsafe_b64encode(ephemeralKey.get_pubkey()))
113+
114+
encryptParams = {
115+
'keyid': ephemeralKeyId,
116+
'dh': staticKey.get_pubkey(),
117+
'salt': os.urandom(16),
118+
'rs': rlen() + 1
119+
}
120+
decryptParams = {
121+
'keyid': staticKeyId,
122+
'dh': ephemeralKey.get_pubkey(),
123+
'salt': encryptParams['salt'],
124+
'rs': encryptParams['rs']
125+
}
126+
127+
encryptDecrypt(rlen(), encryptParams, decryptParams)
128+
129+
if __name__ == '__main__':
130+
for i in range(0,count):
131+
useExplicitKey()
132+
exactlyOneRecord()
133+
detectTruncation()
134+
useKeyId()
135+
useDH()
136+
137+
print 'All tests passed.'

0 commit comments

Comments
 (0)