ece 0.1.2 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +29 -5
- data/Rakefile +9 -1
- data/ece.gemspec +2 -2
- data/lib/ece.rb +0 -1
- data/lib/ece/ece.rb +86 -33
- data/lib/ece/version.rb +2 -2
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e83a743e5e15a8603358906d330f2152763bcdc48d0bb7793ccbb7a8f30cc86c
|
4
|
+
data.tar.gz: 2251ca33088306a752a496d2e5204658daee8a7c2ef7782f119f6840a8fb7d78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 47e9b83318a4e511dc962b8ab1a42e45b351e2d2961a392ae9096425337b55d7df583d05a54c798cfffa0a3294aba0e6fe1426a3cd47725cad155c548ae2b5f6
|
7
|
+
data.tar.gz: '09ac0852fbb993ffa142a1ccfb5d4de2f7c45ea8dc93c0c4d8fe46f07d8b7c3f8024a62c5314ebd7d6fc4e20293eddc385a2b609a6ce88b79e2496388e42551d'
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# ECE
|
2
2
|
|
3
|
-
Ruby implementation of encrypted content-encoding.
|
3
|
+
Ruby implementation of encrypted content-encoding.
|
4
4
|
|
5
5
|
https://tools.ietf.org/html/draft-thomson-http-encryption-02
|
6
6
|
|
@@ -31,14 +31,38 @@ key = Random.new.bytes(16)
|
|
31
31
|
salt = Random.new.bytes(16)
|
32
32
|
data = "Your very private data"
|
33
33
|
|
34
|
-
encrypted_data =
|
34
|
+
encrypted_data = ECE.encrypt(data, key: key, salt: salt)
|
35
35
|
```
|
36
36
|
Decrypting:
|
37
|
+
|
37
38
|
```ruby
|
38
|
-
|
39
|
+
ECE.decrypt(encrypted_data, key: key, salt: salt)
|
39
40
|
```
|
40
41
|
Data can be bytestring as well.
|
42
|
+
|
43
|
+
Encrypting data with elliptical curve Diffie-Hellman (ECDH) key agreement
|
44
|
+
protocol using client keys providing by a [Web Push subscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/getKey):
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
user_public_key # Provided by the browser, effectively: Random.new.bytes(65)
|
48
|
+
user_auth # Provided by the browser, effectively: Random.new.bytes(16)
|
49
|
+
|
50
|
+
local_curve = OpenSSL::PKey::EC.new("prime256v1")
|
51
|
+
local_curve.generate_key
|
52
|
+
user_public_key_point = OpenSSL::PKey::EC::Point.new(local_curve.group, OpenSSL::BN.new(user_public_key, 2))
|
53
|
+
|
54
|
+
key = local_curve.dh_compute_key(user_public_key_point)
|
55
|
+
server_public_key = local_curve.public_key.to_bn.to_s(2)
|
56
|
+
salt = Random.new.bytes(16)
|
57
|
+
|
58
|
+
encrypted_data = ECE.encrypt(data,
|
59
|
+
key: key,
|
60
|
+
salt: salt
|
61
|
+
server_public_key: server_public_key,
|
62
|
+
user_public_key: user_public_key,
|
63
|
+
auth: user_auth)
|
64
|
+
```
|
65
|
+
|
41
66
|
## Contributing
|
42
67
|
|
43
68
|
Bug reports and pull requests are welcome on GitHub at https://github.com/randomlogin/ece.
|
44
|
-
|
data/Rakefile
CHANGED
data/ece.gemspec
CHANGED
@@ -5,7 +5,7 @@ require 'ece/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "ece"
|
8
|
-
spec.version =
|
8
|
+
spec.version = ECE::VERSION
|
9
9
|
spec.authors = ["Alexander Shevtsov"]
|
10
10
|
spec.email = ["randomlogin76@gmail.com"]
|
11
11
|
spec.license = "MIT"
|
@@ -18,6 +18,6 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.require_paths = ["lib"]
|
19
19
|
|
20
20
|
spec.add_development_dependency "bundler", "~> 1.11"
|
21
|
-
spec.add_development_dependency "rake", "~>
|
21
|
+
spec.add_development_dependency "rake", "~> 12.3.3"
|
22
22
|
spec.add_dependency 'hkdf'
|
23
23
|
end
|
data/lib/ece.rb
CHANGED
data/lib/ece/ece.rb
CHANGED
@@ -4,26 +4,45 @@ require 'base64'
|
|
4
4
|
|
5
5
|
#TODO: variable padding
|
6
6
|
|
7
|
-
class
|
7
|
+
class ECE
|
8
8
|
|
9
9
|
KEY_LENGTH=16
|
10
10
|
TAG_LENGTH=16
|
11
11
|
NONCE_LENGTH=12
|
12
|
+
SHA256_LENGTH=32
|
12
13
|
|
13
14
|
def self.hmac_hash(key, input)
|
14
15
|
digest = OpenSSL::Digest.new('sha256')
|
15
16
|
OpenSSL::HMAC.digest(digest, key, input)
|
16
17
|
end
|
17
18
|
|
18
|
-
def self.hkdf_extract(salt, ikm)
|
19
|
+
def self.hkdf_extract(salt, ikm) #ikm stays for input keying material
|
19
20
|
hmac_hash(salt,ikm)
|
20
21
|
end
|
21
22
|
|
23
|
+
def self.get_info(type, client_public, server_public)
|
24
|
+
cl_len_no = [client_public.size].pack('n')
|
25
|
+
sv_len_no = [server_public.size].pack('n')
|
26
|
+
"Content-Encoding: #{type}\x00P-256\x00#{cl_len_no}#{client_public}#{sv_len_no}#{server_public}"
|
27
|
+
end
|
28
|
+
|
22
29
|
def self.extract_key(params)
|
23
30
|
raise "Salt must be 16-bytes long" unless params[:salt].length==16
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
|
32
|
+
input_key = params[:key]
|
33
|
+
auth = false
|
34
|
+
if params.has_key?(:auth) # Encrypted Content Encoding, March 11 2016, http://httpwg.org/http-extensions/draft-ietf-httpbis-encryption-encoding.html
|
35
|
+
auth = true
|
36
|
+
input = HKDF.new(input_key, {salt: params[:auth] , algorithm: 'sha256', info: "Content-Encoding: auth\x00"})
|
37
|
+
input_key = input.next_bytes(SHA256_LENGTH)
|
38
|
+
secret = HKDF.new(input_key, {salt: params[:salt], algorithm: 'sha256', info: get_info("aesgcm", params[:user_public_key], params[:server_public_key])})
|
39
|
+
nonce = HKDF.new(input_key, salt: params[:salt], algorithm: 'sha256', info: get_info("nonce", params[:user_public_key], params[:server_public_key]))
|
40
|
+
else
|
41
|
+
secret = HKDF.new(input_key, {salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: aesgcm128"})
|
42
|
+
nonce = HKDF.new(input_key, salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: nonce")
|
43
|
+
end
|
44
|
+
|
45
|
+
{key: secret.next_bytes(KEY_LENGTH), nonce: nonce.next_bytes(NONCE_LENGTH), auth: auth}
|
27
46
|
end
|
28
47
|
|
29
48
|
def self.generate_nonce(nonce, counter)
|
@@ -31,27 +50,42 @@ class Ece
|
|
31
50
|
output = nonce.dup
|
32
51
|
integer = nonce[-6..-1].unpack('B*')[0].to_i(2) #taking last 6 bytes, treating as integer
|
33
52
|
x = ((integer ^ counter) & 0xffffff) + ((((integer / 0x1000000) ^ (counter / 0x1000000)) & 0xffffff) * 0x1000000)
|
34
|
-
|
35
|
-
output
|
53
|
+
bytestring = x.to_s(16).length < 12 ? "0"*(12-x.to_s(16).length)+x.to_s(16) : x.to_s(16) #it's for correct handling of cases when generated integer is less than 6 bytes
|
54
|
+
output[-6..-1] = [bytestring].pack('H*') #without it packing would produce less than 6 bytes
|
55
|
+
output #I didn't find pack directive for such usage, so there is a such solution
|
36
56
|
end
|
37
57
|
|
38
|
-
def self.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
58
|
+
def self.encrypt(data, params)
|
59
|
+
key = extract_key(params)
|
60
|
+
rs = params[:rs] ? params [:rs] : 4096
|
61
|
+
padsize = params[:padsize] ? params [:padsize] : 0
|
62
|
+
raise "The rs parameter must be greater than 1." if rs <= 1
|
63
|
+
rs -=1 #this ensures encrypted data cannot be truncated
|
64
|
+
result = ""
|
65
|
+
pad_bytes = 1
|
66
|
+
if params[:auth] # old spec used 1 byte for padding, latest one always uses 2 bytes
|
67
|
+
pad_bytes = 2
|
68
|
+
end
|
69
|
+
|
70
|
+
counter = 0
|
71
|
+
(0..data.length).step(rs-pad_bytes+1) do |i|
|
72
|
+
block = encrypt_record(key, counter, data[i..i+rs-pad_bytes], padsize)
|
73
|
+
result += block
|
74
|
+
counter +=1
|
75
|
+
end
|
76
|
+
result
|
46
77
|
end
|
47
78
|
|
48
|
-
def self.
|
79
|
+
def self.decrypt(data, params)
|
49
80
|
key = extract_key(params)
|
50
|
-
rs =
|
81
|
+
rs = params[:rs] ? params [:rs] : 4096
|
82
|
+
raise "The rs parameter must be greater than 1." if rs <= 1
|
83
|
+
rs += TAG_LENGTH
|
84
|
+
raise "Message is truncated" if data.length % rs == 0
|
51
85
|
result = ""
|
52
86
|
counter = 0
|
53
87
|
(0..data.length).step(rs) do |i|
|
54
|
-
block =
|
88
|
+
block = decrypt_record(key, counter, data[i..i+rs-1])
|
55
89
|
result += block
|
56
90
|
counter +=1
|
57
91
|
end
|
@@ -59,30 +93,49 @@ class Ece
|
|
59
93
|
end
|
60
94
|
|
61
95
|
def self.decrypt_record(params, counter, buffer, pad=0)
|
62
|
-
raise "Key must be #{KEY_LENGTH} bytes long" unless params[:key].length == KEY_LENGTH
|
63
96
|
gcm = OpenSSL::Cipher.new('aes-128-gcm')
|
64
97
|
gcm.decrypt
|
65
98
|
gcm.key = params[:key]
|
66
99
|
gcm.iv = generate_nonce(params[:nonce], counter)
|
100
|
+
pad_bytes = 1
|
101
|
+
if params[:auth] # old spec used 1 byte for padding, latest one always uses 2 bytes
|
102
|
+
pad_bytes = 2
|
103
|
+
end
|
104
|
+
raise "Block is too small" if buffer.length <= TAG_LENGTH+pad_bytes
|
67
105
|
gcm.auth_tag = buffer[-TAG_LENGTH..-1]
|
68
106
|
decrypted = gcm.update(buffer[0..-TAG_LENGTH-1]) + gcm.final
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
107
|
+
|
108
|
+
if params[:auth]
|
109
|
+
padding_length = decrypted[0..1].unpack("n")[0]
|
110
|
+
raise "Padding is too big" if padding_length+2 > decrypted.length
|
111
|
+
padding = decrypted[2..padding_length]
|
112
|
+
raise "Wrong padding" unless padding = "\x00"*padding_length
|
113
|
+
return decrypted[2+padding_length..-1]
|
114
|
+
else
|
115
|
+
padding_length = decrypted[0].unpack("C")[0]
|
116
|
+
raise "Padding is too big" if padding_length+1 > decrypted.length
|
117
|
+
padding = decrypted[1..padding_length]
|
118
|
+
raise "Wrong padding" unless padding = "\x00"*padding_length
|
119
|
+
return decrypted[1..-1]
|
120
|
+
end
|
73
121
|
end
|
74
122
|
|
75
|
-
def self.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
123
|
+
def self.encrypt_record(params, counter, buffer, pad=0)
|
124
|
+
gcm = OpenSSL::Cipher.new('aes-128-gcm')
|
125
|
+
gcm.encrypt
|
126
|
+
gcm.key = params[:key]
|
127
|
+
gcm.iv = generate_nonce(params[:nonce], counter)
|
128
|
+
gcm.auth_data = ""
|
129
|
+
padding = ""
|
130
|
+
if params[:auth]
|
131
|
+
padding = [pad].pack('n') + "\x00"*pad # 2 bytes, big endian, then n zero bytes of padding
|
132
|
+
buf = padding+buffer
|
133
|
+
record = gcm.update(buf)
|
134
|
+
else
|
135
|
+
record = gcm.update("\x00"+buffer) # 1 padding byte, not fully implemented
|
84
136
|
end
|
85
|
-
|
137
|
+
enc = record + gcm.final + gcm.auth_tag
|
138
|
+
enc
|
86
139
|
end
|
87
140
|
|
88
141
|
end
|
data/lib/ece/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
class
|
2
|
-
VERSION = "0.
|
1
|
+
class ECE
|
2
|
+
VERSION = "0.2.2"
|
3
3
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ece
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Shevtsov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 12.3.3
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 12.3.3
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: hkdf
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,8 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '0'
|
89
89
|
requirements: []
|
90
|
-
|
91
|
-
rubygems_version: 2.4.8
|
90
|
+
rubygems_version: 3.1.2
|
92
91
|
signing_key:
|
93
92
|
specification_version: 4
|
94
93
|
summary: Ruby implementation of encrypted content-encoding
|