Skip to content

Commit 7fdb46a

Browse files
committed
Moving to a 2-byte padding
1 parent 3ac76b6 commit 7fdb46a

7 files changed

Lines changed: 139 additions & 112 deletions

File tree

nodejs/decrypt-dh.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 < 7) {
8+
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9+
' <receiver-private> <receiver-public> <sender-public> <salt> <message> [JSON args]');
10+
process.exit(2);
11+
}
12+
13+
var receiver = crypto.createECDH('prime256v1');
14+
// node crypto is finicky about accessing the public key
15+
// 1. it can't generate the public key from the private key
16+
// 2. it barfs when you try to access the public key, even after you set it
17+
// This hack squelches the complaints at the cost of a few wasted cycles
18+
receiver.generateKeys();
19+
receiver.setPublicKey(base64.decode(process.argv[3]));
20+
receiver.setPrivateKey(base64.decode(process.argv[2]));
21+
ece.saveKey('keyid', receiver, "P-256");
22+
23+
var params = {
24+
keyid: 'keyid',
25+
dh: process.argv[4],
26+
salt: process.argv[5]
27+
};
28+
29+
if (process.argv.length > 7) {
30+
var extra = JSON.parse(process.argv[7]);
31+
Object.keys(extra).forEach(function(k) {
32+
params[k] = extra[k];
33+
});
34+
}
35+
36+
console.log("Params: " + JSON.stringify(params, null, 2));
37+
var result = ece.decrypt(base64.decode(process.argv[6]), params);
38+
39+
console.log(base64.encode(result));
40+
console.log(result.toString('utf-8'));

nodejs/decrypt.js

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,26 @@ var base64 = require('urlsafe-base64');
44
var crypto = require('crypto');
55
var ece = require('./ece.js');
66

7-
if (process.argv.length < 7) {
7+
if (process.argv.length < 5) {
88
console.warn('Usage: ' + process.argv.slice(0, 2).join(' ') +
9-
' <receiver-private> <receiver-public> <sender-public> <salt> <message> [JSON args]');
9+
' <key> <salt> <message> [JSON args]');
1010
process.exit(2);
1111
}
1212

13-
var receiver = crypto.createECDH('prime256v1');
14-
// node crypto is finicky about accessing the public key
15-
// 1. it can't generate the public key from the private key
16-
// 2. it barfs when you try to access the public key, even after you set it
17-
// This hack squelches the complaints at the cost of a few wasted cycles
18-
receiver.generateKeys();
19-
receiver.setPublicKey(base64.decode(process.argv[3]));
20-
receiver.setPrivateKey(base64.decode(process.argv[2]));
21-
ece.saveKey('keyid', receiver, "P-256");
22-
2313
var params = {
24-
keyid: 'keyid',
25-
dh: process.argv[4],
26-
salt: process.argv[5]
14+
key: process.argv[2],
15+
salt: process.argv[3]
2716
};
2817

29-
if (process.argv.length > 7) {
30-
var extra = JSON.parse(process.argv[7]);
18+
if (process.argv.length > 5) {
19+
var extra = JSON.parse(process.argv[5]);
3120
Object.keys(extra).forEach(function(k) {
3221
params[k] = extra[k];
3322
});
3423
}
3524

3625
console.log("Params: " + JSON.stringify(params, null, 2));
37-
var result = ece.decrypt(base64.decode(process.argv[6]), params);
26+
var result = ece.decrypt(base64.decode(process.argv[4]), params);
3827

3928
console.log(base64.encode(result));
4029
console.log(result.toString('utf-8'));

nodejs/ece.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ var base64 = require('urlsafe-base64');
66
var savedKeys = {};
77
var keyLabels = {};
88
var AES_GCM = 'id-aes128-GCM';
9+
var PAD_SIZE = 2;
910
var TAG_LENGTH = 16;
1011
var KEY_LENGTH = 16;
1112
var NONCE_LENGTH = 12;
@@ -135,8 +136,8 @@ function determineRecordSize(params) {
135136
if (isNaN(rs)) {
136137
return 4096;
137138
}
138-
if (rs <= 1) {
139-
throw new Error('The rs parameter has to be greater than 1');
139+
if (rs <= PAD_SIZE) {
140+
throw new Error('The rs parameter has to be greater than ' + PAD_SIZE);
140141
}
141142
return rs;
142143
}
@@ -156,16 +157,16 @@ function decryptRecord(key, counter, buffer) {
156157
gcm.setAuthTag(buffer.slice(buffer.length - TAG_LENGTH));
157158
var data = gcm.update(buffer.slice(0, buffer.length - TAG_LENGTH));
158159
data = Buffer.concat([data, gcm.final()]);
159-
var pad = data.readUInt8(0);
160-
if (pad + 1 > data.length) {
160+
var pad = data.readUIntBE(0, PAD_SIZE);
161+
if (pad + PAD_SIZE > data.length) {
161162
throw new Error('padding exceeds block size');
162163
}
163164
var padCheck = new Buffer(pad);
164165
padCheck.fill(0);
165-
if (padCheck.compare(data.slice(1, 1 + pad)) !== 0) {
166+
if (padCheck.compare(data.slice(PAD_SIZE, PAD_SIZE + pad)) !== 0) {
166167
throw new Error('invalid padding');
167168
}
168-
return data.slice(1 + pad);
169+
return data.slice(PAD_SIZE + pad);
169170
}
170171

171172
// TODO: this really should use the node streams stuff
@@ -205,9 +206,9 @@ function encryptRecord(key, counter, buffer, pad) {
205206
pad = pad || 0;
206207
var nonce = generateNonce(key.nonce, counter);
207208
var gcm = crypto.createCipheriv(AES_GCM, key.key, nonce);
208-
var padding = new Buffer(pad + 1);
209+
var padding = new Buffer(pad + PAD_SIZE);
209210
padding.fill(0);
210-
padding.writeUIntBE(pad, 0, 1);
211+
padding.writeUIntBE(pad, 0, PAD_SIZE);
211212
var epadding = gcm.update(padding);
212213
var ebuffer = gcm.update(buffer);
213214
gcm.final();
@@ -236,13 +237,14 @@ function encrypt(buffer, params) {
236237
// of a buffer.
237238
for (var i = 0; start <= buffer.length; ++i) {
238239
// Pad so that at least one data byte is in a block.
239-
var recordPad = Math.min(255, Math.min(rs - 2, pad));
240+
var recordPad = Math.min((1 << (PAD_SIZE * 8)) - 1, // maximum padding
241+
Math.min(rs - PAD_SIZE - 1, pad));
240242
pad -= recordPad;
241243

242-
var end = Math.min(start + rs - 1 - recordPad, buffer.length);
244+
var end = Math.min(start + rs - PAD_SIZE - recordPad, buffer.length);
243245
var block = encryptRecord(key, i, buffer.slice(start, end), recordPad);
244246
result = Buffer.concat([result, block]);
245-
start += rs - 1 - recordPad;
247+
start += rs - PAD_SIZE - recordPad;
246248
}
247249
if (pad) {
248250
throw new Error('Unable to pad by requested amount, ' + pad + ' remaining');
File renamed without changes.

nodejs/test.js

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,28 @@ if (count === 1) {
2222
} else {
2323
log = function() {};
2424
}
25+
function logbuf(msg, buf) {
26+
if (typeof buf === 'string') {
27+
buf = base64.decode(buf);
28+
}
29+
log(msg + ': [' + buf.length + ']');
30+
for (i = 0; i < buf.length; i += 48) {
31+
log(' ' + base64.encode(buf.slice(i, i + 48)));
32+
}
33+
}
2534

2635
function encryptDecrypt(length, encryptParams, decryptParams) {
2736
decryptParams = decryptParams || encryptParams;
28-
log("Salt: " + base64.encode(encryptParams.salt));
37+
logbuf('Salt', encryptParams.salt);
2938
var input = plaintext || crypto.randomBytes(Math.min(length, maxLen));
3039
// var input = new Buffer('I am the walrus');
31-
log("Input: " + base64.encode(input));
40+
logbuf('Input', input);
3241
var encrypted = ece.encrypt(input, encryptParams);
33-
log("Encrypted: " + base64.encode(encrypted));
42+
logbuf('Encrypted', encrypted);
3443
var decrypted = ece.decrypt(encrypted, decryptParams);
35-
log("Decrypted: " + base64.encode(decrypted));
44+
logbuf('Decrypted', decrypted);
3645
assert.equal(Buffer.compare(input, decrypted), 0);
37-
log("----- OK");
46+
log('----- OK');
3847
}
3948

4049
function useExplicitKey() {
@@ -44,7 +53,7 @@ function useExplicitKey() {
4453
salt: base64.encode(crypto.randomBytes(16)),
4554
rs: length.readUInt16BE(0) + 1
4655
};
47-
log('Key: ' + base64.encode(params.key));
56+
logbuf('Key', params.key);
4857
encryptDecrypt(length.readUInt16BE(2), params);
4958
}
5059

@@ -56,8 +65,8 @@ function authenticationSecret() {
5665
rs: length.readUInt16BE(0) + 1,
5766
authSecret: base64.encode(crypto.randomBytes(16))
5867
};
59-
log('Key: ' + base64.encode(params.key));
60-
log('Context: ' + base64.encode(params.authSecret));
68+
logbuf('Key', params.key);
69+
logbuf('Context', params.authSecret);
6170
encryptDecrypt(length.readUInt16BE(2), params);
6271
}
6372

@@ -78,12 +87,12 @@ function detectTruncation() {
7887
salt: base64.encode(crypto.randomBytes(16)),
7988
rs: length + 1
8089
};
81-
log("Salt: " + base64.encode(params.salt));
90+
logbuf('Salt', params.salt);
8291
var input = crypto.randomBytes(Math.min(length, maxLen));
83-
log("Input: " + base64.encode(input));
92+
logbuf('Input', input);
8493
var encrypted = ece.encrypt(input, params);
8594
encrypted = encrypted.slice(0, length + 1 + 16);
86-
log("Encrypted: " + base64.encode(encrypted));
95+
logbuf('Encrypted', encrypted);
8796
var ok = false;
8897
try {
8998
ece.decrypt(encrypted, params);
@@ -117,8 +126,8 @@ function useDH() {
117126
var staticKeyId = staticKey.getPublicKey().toString('hex')
118127
ece.saveKey(staticKeyId, staticKey, 'P-256');
119128

120-
log("Receiver private: " + base64.encode(staticKey.getPrivateKey()));
121-
log("Receiver public: " + base64.encode(staticKey.getPublicKey()));
129+
logbuf('Receiver private', staticKey.getPrivateKey());
130+
logbuf('Receiver public', staticKey.getPublicKey());
122131

123132
// the ephemeral key is used by the sender
124133
var ephemeralKey = crypto.createECDH('prime256v1');
@@ -127,8 +136,8 @@ function useDH() {
127136
var ephemeralKeyId = ephemeralKey.getPublicKey().toString('hex');
128137
ece.saveKey(ephemeralKeyId, ephemeralKey, 'P-256');
129138

130-
log("Sender private: " + base64.encode(ephemeralKey.getPrivateKey()));
131-
log("Sender public: " + base64.encode(ephemeralKey.getPublicKey()));
139+
logbuf('Sender private', ephemeralKey.getPrivateKey());
140+
logbuf('Sender public', ephemeralKey.getPublicKey());
132141

133142
var length = crypto.randomBytes(4);
134143
var encryptParams = {
@@ -148,12 +157,16 @@ function useDH() {
148157

149158
var i;
150159
for (i = 0; i < count; ++i) {
151-
useExplicitKey();
152-
authenticationSecret();
153-
exactlyOneRecord();
154-
detectTruncation();
155-
useKeyId();
156-
useDH();
160+
[ useExplicitKey,
161+
authenticationSecret,
162+
exactlyOneRecord,
163+
detectTruncation,
164+
useKeyId,
165+
useDH,
166+
].forEach(function(f) {
167+
log('Test: ' + f.name);
168+
f();
169+
});
157170
}
158171

159172
console.log('All tests passed.');

python/http_ece/__init__.py

Lines changed: 17 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99

1010
keys = {}
1111
labels = {}
12+
padSize = 2
1213

13-
def deriveKey(mode, salt, key=None, dh=None, keyid=None, authSecret=b""):
14+
def deriveKey(mode, salt, key=None, dh=None, keyid=None, authSecret=None):
1415
def buildInfo(base, context):
1516
return b"Content-Encoding: " + base + b"\0" + context
1617

@@ -77,32 +78,33 @@ def lengthPrefix(key):
7778
info=buildInfo(b"nonce", context),
7879
backend=default_backend()
7980
)
80-
return (hkdf_key.derive(secret), hkdf_nonce.derive(secret))
81+
result = (hkdf_key.derive(secret), hkdf_nonce.derive(secret))
82+
return result
8183

8284
def iv(base, counter):
8385
if (counter >> 64) != 0:
8486
raise Exception(u"Counter too big")
8587
(mask,) = struct.unpack("!Q", base[4:])
8688
return base[:4] + struct.pack("!Q", counter ^ mask)
8789

88-
def decrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096, authSecret=b""):
90+
def decrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096, authSecret=None):
8991
def decryptRecord(key, nonce, counter, buffer):
9092
decryptor = Cipher(
9193
algorithms.AES(key),
9294
modes.GCM(iv(nonce, counter), tag=buffer[-16:]),
9395
backend=default_backend()
9496
).decryptor()
9597
data = decryptor.update(buffer[:-16]) + decryptor.finalize()
96-
(pad,) = struct.unpack("!B", data[0:1]);
97-
if data[1:1+pad] != (b"\x00" * pad):
98+
(pad,) = struct.unpack("!H", data[0:padSize]);
99+
if data[padSize:padSize+pad] != (b"\x00" * pad):
98100
raise Exception(u"Bad padding")
99-
data = data[1+pad:]
101+
data = data[padSize+pad:]
100102
return data
101103

102104
(key_, nonce_) = deriveKey(mode="decrypt", salt=salt,
103105
key=key, keyid=keyid, dh=dh,
104106
authSecret=authSecret)
105-
if rs < 2:
107+
if rs <= padSize:
106108
raise Exception(u"Record size too small")
107109
rs += 16 # account for tags
108110
if len(buffer) % rs == 0:
@@ -115,58 +117,29 @@ def decryptRecord(key, nonce, counter, buffer):
115117
counter += 1
116118
return result
117119

118-
def encrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096, authSecret=b""):
120+
def encrypt(buffer, salt, key=None, keyid=None, dh=None, rs=4096, authSecret=None):
119121
def encryptRecord(key, nonce, counter, buffer):
120122
encryptor = Cipher(
121123
algorithms.AES(key),
122124
modes.GCM(iv(nonce, counter)),
123125
backend=default_backend()
124126
).encryptor()
125-
data = encryptor.update(b"\x00" + buffer) + encryptor.finalize()
127+
data = encryptor.update(b"\0\0" + buffer) + encryptor.finalize()
126128
data += encryptor.tag
127129
return data
128130

129131
(key_, nonce_) = deriveKey(mode="encrypt", salt=salt,
130132
key=key, keyid=keyid, dh=dh,
131133
authSecret=authSecret)
132-
if rs < 2:
134+
if rs <= padSize:
133135
raise Exception(u"Record size too small")
134-
rs -= 1 # account for padding
136+
rs -= padSize # account for padding
135137

136138
result = b""
137139
counter = 0
138-
# the extra one ensures that we produce a padding only record if the data
139-
# length is an exact multiple of rs-1
140-
for i in list(range(0, len(buffer) + 1, rs)):
140+
# the extra padSize on the loop ensures that we produce a padding only record if the data
141+
# length is an exact multiple of rs-padSize
142+
for i in list(range(0, len(buffer) + padSize, rs)):
141143
result += encryptRecord(key_, nonce_, counter, buffer[i:i+rs])
142-
++counter
144+
counter += 1
143145
return result
144-
145-
if __name__ == "__main__":
146-
import base64
147-
148-
salt=base64.urlsafe_b64decode("mUFsKgrmI-i_-HowjX_2XA==")
149-
key=base64.urlsafe_b64decode("F-hAEGCm7KIGUiSdS4GGtA==")
150-
m = base64.urlsafe_b64decode("iEPbDBuohQLznv45IlaF1eLRCeu6aWfsq-pDP7OnzgH4A0x5lyIEVAfM39RgeLekW1VgZWIFL_WvuveEhaHj0-iEvxDHw_apYGFYWEY6KmMhXgWPmFZ-2wAMnDsQ-DDVbZHsXw==")
151-
rs=3300
152-
print ("message", len(m), base64.urlsafe_b64encode(m))
153-
e = encrypt(m, salt=salt, key=key, rs=rs)
154-
print ("encrypted", len(e), base64.urlsafe_b64encode(e))
155-
d = decrypt(e, salt=salt, key=key, rs=rs)
156-
print ("decrypted", len(d), base64.urlsafe_b64encode(d))
157-
print (m == d)
158-
159-
salt = os.urandom(16)
160-
print ("salt", base64.urlsafe_b64encode(salt))
161-
keys["receiver"] = pyelliptic.ECC(curve="prime256v1")
162-
print ("receiver", base64.urlsafe_b64encode(keys["receiver"].get_pubkey()),
163-
base64.urlsafe_b64encode(keys["receiver"].get_privkey()))
164-
keys["sender"] = pyelliptic.ECC(curve="prime256v1")
165-
print ("sender", base64.urlsafe_b64encode(keys["sender"].get_pubkey()),
166-
base64.urlsafe_b64encode(keys["sender"].get_privkey()))
167-
168-
e = encrypt(m, salt=salt, keyid="sender", dh=keys["receiver"].get_pubkey(), rs=rs)
169-
print ("encrypted", len(e), base64.urlsafe_b64encode(e))
170-
d = decrypt(e, salt=salt, keyid="receiver", dh=keys["sender"].get_pubkey(), rs=rs)
171-
print ("decrypted", len(d), base64.urlsafe_b64encode(d))
172-
print (m == d)

0 commit comments

Comments
 (0)