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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9d5b4405985580581acc70aecdc06679e790a0c2
4
- data.tar.gz: d2e1bf3c52a065fb8cdc84630ab841da02921531
2
+ SHA256:
3
+ metadata.gz: e83a743e5e15a8603358906d330f2152763bcdc48d0bb7793ccbb7a8f30cc86c
4
+ data.tar.gz: 2251ca33088306a752a496d2e5204658daee8a7c2ef7782f119f6840a8fb7d78
5
5
  SHA512:
6
- metadata.gz: 72508ea873779630f61ec4308112c4d950681bf0db93a33eb7af9225ec09c422c389d2c06076b70dcecc458f60578e1f5ea0ed4a8b0ada22c444329f371b198e
7
- data.tar.gz: ce9d268b04c7a2bd3a75e06775a74a86c7eb6b38d232859226da25afab44bee58a4a96a8800d54c253e5373a5f972745d3ea15a210b0e3dc986a8affcefe459a
6
+ metadata.gz: 47e9b83318a4e511dc962b8ab1a42e45b351e2d2961a392ae9096425337b55d7df583d05a54c798cfffa0a3294aba0e6fe1426a3cd47725cad155c548ae2b5f6
7
+ data.tar.gz: '09ac0852fbb993ffa142a1ccfb5d4de2f7c45ea8dc93c0c4d8fe46f07d8b7c3f8024a62c5314ebd7d6fc4e20293eddc385a2b609a6ce88b79e2496388e42551d'
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Ece
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 = Ece.encrypt(data, key: key, salt: salt)
34
+ encrypted_data = ECE.encrypt(data, key: key, salt: salt)
35
35
  ```
36
36
  Decrypting:
37
+
37
38
  ```ruby
38
- Ece.decrypt(encrypted_data, key: key, salt: salt)
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
@@ -1,2 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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 = Ece::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", "~> 10.0"
21
+ spec.add_development_dependency "rake", "~> 12.3.3"
22
22
  spec.add_dependency 'hkdf'
23
23
  end
data/lib/ece.rb CHANGED
@@ -1,3 +1,2 @@
1
1
  require 'ece/version'
2
2
  require 'ece/ece'
3
-
data/lib/ece/ece.rb CHANGED
@@ -4,26 +4,45 @@ require 'base64'
4
4
 
5
5
  #TODO: variable padding
6
6
 
7
- class Ece
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
- key = HKDF.new(params[:key], salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: aesgcm128")
25
- nonce = HKDF.new(params[:key], salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: nonce")
26
- {key: key.next_bytes(KEY_LENGTH), nonce: nonce.next_bytes(NONCE_LENGTH)}
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
- output[-6..-1] = [x.to_s(16)].pack('H*')
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.encrypt_record(params, counter, buffer, pad=0)
39
- raise "Key must be #{KEY_LENGTH} bytes long" unless params[:key].length == KEY_LENGTH
40
- gcm = OpenSSL::Cipher.new('aes-128-gcm')
41
- gcm.encrypt
42
- gcm.key = params[:key]
43
- gcm.iv = generate_nonce(params[:nonce], counter)
44
- enc = gcm.update("\x00"+buffer) + gcm.final + gcm.auth_tag #enc = gcm.update("\x00"*pad+buffer)+gcm.final + gcm.auth_tag padding is not fully implemented for now
45
- enc
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.encrypt(data, params)
79
+ def self.decrypt(data, params)
49
80
  key = extract_key(params)
50
- rs = 4095 #should be variable, but for now it's constant
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 = encrypt_record(key, counter, data[i..i+rs-1])
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
- #padding = decrypted[0] -- this would be used once variable record-size is implemented
70
- #padding_length = decrypted[0].unpack("C")
71
- #raise Err unless padding = "\x00"*padding_length
72
- decrypted[1..-1]
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.decrypt(data, params)
76
- key = extract_key(params)
77
- rs = 4096+16 #not changeable for now
78
- result = ""
79
- counter = 0
80
- (0..data.length).step(rs) do |i|
81
- block = decrypt_record(key, counter, data[i..i+rs-1])
82
- result += block
83
- counter +=1
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
- result
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 Ece
2
- VERSION = "0.1.2"
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.1.2
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: 2015-12-21 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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
- rubyforge_project:
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