urlcrypt 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43b85d8e2005d9c5fb0b2181f9865acb54c85123b9e4aeac7c7c66360aedbdfd
4
- data.tar.gz: '008bc1517bf2fd48531d97bdfa4738520b59c855f145716238bd92edc5ba52bc'
3
+ metadata.gz: 4b88fc478e4e9f1be07a35951eb871bfa2cacc5ca3d6478d95dff1ea24df138d
4
+ data.tar.gz: 6f01a73f86568c9baec085265539c0b889007ea570273b78609924fc1939a865
5
5
  SHA512:
6
- metadata.gz: 9c663737786ae94458f6a28442936fd6bdcdde71bbb198d13c1693d151a028d03d647ff080ccdb2b243fa49310b6cc97b95ea305abc28fd22d46d918f10e00e0
7
- data.tar.gz: 92567f8985d16055720cea95be351e0a675c764734ae9aba366cb85f360d763d623e44136385c09edece332765eb417f5f79be943a2046abcf466323baf6b110
6
+ metadata.gz: b1a7d56dd3bcf4e27a396d3949e9e8cd546cc70a68bff62444ffee6d6e7a8bc1c8370ae94ba28fc24e4d37c01c979cd989d81370854dd7d10aff1489dac39ec7
7
+ data.tar.gz: a1322a2ebf8c4965c1f6945ba7bf9e61fde1566d5ea33fc05668897a0eb5c3133a83f5dc23ea5d649b7024b06dfec9b8dd0f0f79f04bd8b525919e81be7e636b
data/README.md CHANGED
@@ -20,7 +20,7 @@ that doesn't have other authentication or persistence mechanisms (like cookies):
20
20
  * Links that come with an expiration date (à la S3)
21
21
  * Mini-apps that don't persist data on the server
22
22
 
23
- Works with Ruby 2.1+
23
+ Works with Ruby 2.6+
24
24
 
25
25
  **Important**: As a general guideline, URL lengths shouldn't exceed about 2000
26
26
  characters in length, as URLs longer than that will not work in some browsers
@@ -41,18 +41,21 @@ Add to your Gemfile:
41
41
  gem 'urlcrypt', '~> 0.1.1', require: 'URLcrypt'
42
42
  ```
43
43
 
44
+ Then, set `ENV['urlcrypt_key']` to the default encryption key you will be using. This should be at least a 256-bit AES key, see below. **To ensure your strings are encoded, URLcrypt uses `ENV.fetch` to check for the variable, so it _must_ be set.**
45
+
44
46
  ## Example
45
47
 
46
48
  ```ruby
47
- # encrypt and encode with 256-bit AES
48
- # one-time setup, set this to a securely random key with at least 256 bits, see below
49
- URLcrypt.key = '...'
50
-
51
- # now encrypt and decrypt!
49
+ # encrypt and decrypt using the default key from ENV['urlcrypt_key']!
52
50
  URLcrypt.encrypt('chunky bacon!') # => "sgmt40kbmnh1663nvwknxk5l0mZ6Av2ndhgw80rkypnp17xmmg5hy"
53
51
  URLcrypt.decrypt('sgmt40kbmnh1663nvwknxk5l0mZ6Av2ndhgw80rkypnp17xmmg5hy')
54
52
  # => "chunky bacon!"
55
53
 
54
+ # If needed, you can specify a custom key per-call
55
+ URLcrypt.encrypt('chunky bacon!', key: '...') # => "....."
56
+ URLcrypt.decrypt('sgmt40kbmnh1663nvwknxk5l0mZ6Av2ndhgw80rkypnp17xmmg5hy', key: '...')
57
+ # => "chunky bacon!"
58
+
56
59
  # encoding without encryption (don't use for anything sensitive!), doesn't need key set
57
60
  URLcrypt.encode('chunky bacon!') # => "mnAhk6tlp2qg2yldn8xcc"
58
61
  URLcrypt.decode('mnAhk6tlp2qg2yldn8xcc') # => "chunky bacon!"
@@ -70,7 +73,7 @@ ba7f56f8f9873b1653d7f032cc474938fd749ee8fbbf731a7c41d698826aca3cebfffa832be7e6bc
70
73
  To use the key with URLcrypt, you'll need to convert that from a hex string into a real byte array:
71
74
 
72
75
  ```ruby
73
- URLcrypt::key = ['longhexkeygoeshere'].pack('H*')
76
+ ENV['urlcrypt_key'] = ['longhexkeygoeshere'].pack('H*')
74
77
  ```
75
78
 
76
79
  ## Running the Test Suite
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2013 Thomas Fuchs
1
+ # Copyright (c) 2013-2022 Thomas Fuchs
2
2
  # Copyright (c) 2007-2011 Samuel Tesla
3
3
 
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -33,12 +33,11 @@ gemspec = Gem::Specification.new do |s|
33
33
  s.email = "thomas@slash7.com"
34
34
  s.extra_rdoc_files = ["README.md"]
35
35
  s.files = FileList["Rakefile", "{config,lib,test}/**/*"]
36
- s.has_rdoc = true
37
36
  s.name = 'urlcrypt'
38
37
  s.require_paths << 'lib'
39
38
  s.requirements << 'none'
40
39
  s.summary = "Securely encode and decode short pieces of arbitrary binary data in URLs."
41
- s.version = "0.1.2"
40
+ s.version = "0.2.0"
42
41
  end
43
42
 
44
43
  Gem::PackageTask.new(gemspec) do |pkg|
data/lib/URLcrypt.rb CHANGED
@@ -2,14 +2,10 @@ require 'openssl'
2
2
 
3
3
  module URLcrypt
4
4
  # avoid vowels to not generate four-letter words, etc.
5
- # this is important because those words can trigger spam
5
+ # this is important because those words can trigger spam
6
6
  # filters when URLs are used in emails
7
7
  TABLE = "1bcd2fgh3jklmn4pqrstAvwxyz567890".freeze
8
8
 
9
- def self.key=(key)
10
- @key = key
11
- end
12
-
13
9
  class Chunk
14
10
  def initialize(bytes)
15
11
  @bytes = bytes
@@ -31,7 +27,11 @@ module URLcrypt
31
27
  [(0..n-1).to_a.reverse.collect {|i| TABLE[(c >> i * 5) & 0x1f].chr},
32
28
  ("=" * (8-n))] # TODO: remove '=' padding generation
33
29
  end
34
-
30
+ end
31
+
32
+ def self.key=(key)
33
+ warn "`URLcrypt.key=` is deprecated. See the README on using environment variables with URLcrypt."
34
+ ENV['urlcrypt_key'] = [key].pack('H*')
35
35
  end
36
36
 
37
37
  def self.chunks(str, size)
@@ -52,27 +52,26 @@ module URLcrypt
52
52
  def self.decode(data)
53
53
  chunks(data, 8).collect(&:decode).flatten.join
54
54
  end
55
-
56
- def self.decrypt(data)
55
+
56
+ def self.decrypt(data, key: ENV.fetch('urlcrypt_key'))
57
57
  iv, encrypted = data.split('Z').map{|part| decode(part)}
58
58
  fail DecryptError, "not a valid string to decrypt" unless iv && encrypted
59
- decrypter = cipher(:decrypt)
59
+ decrypter = cipher(:decrypt, key: key)
60
60
  decrypter.iv = iv
61
- decrypter.update(encrypted) + decrypter.final
61
+ decrypter.update(encrypted) + decrypter.final
62
62
  end
63
-
64
- def self.encrypt(data)
65
- crypter = cipher(:encrypt)
63
+
64
+ def self.encrypt(data, key: ENV.fetch('urlcrypt_key'))
65
+ crypter = cipher(:encrypt, key: key)
66
66
  crypter.iv = iv = crypter.random_iv
67
67
  "#{encode(iv)}Z#{encode(crypter.update(data) + crypter.final)}"
68
68
  end
69
-
70
- private
71
-
72
- def self.cipher(mode)
69
+
70
+ private
71
+ def self.cipher(mode, key:)
73
72
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
74
73
  cipher.send(mode)
75
- cipher.key = @key.byteslice(0,cipher.key_len)
74
+ cipher.key = key.byteslice(0,cipher.key_len)
76
75
  cipher
77
76
  end
78
77
 
@@ -23,23 +23,9 @@ class TestURLcrypt < TestClass
23
23
  assert_decoding(encoded, original)
24
24
  end
25
25
  end
26
-
27
- def test_encryption
28
- # pack() converts this secret into a byte array
29
- secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
30
- URLcrypt::key = secret
31
26
 
32
- assert_equal OpenSSL::Cipher.new('aes-256-cbc').key_len, secret.bytesize
33
-
34
- original = "hello world!"
35
- encrypted = URLcrypt::encrypt(original)
36
- assert_equal(URLcrypt::decrypt(encrypted), original)
37
- end
38
-
39
- def test_decrypt_error
40
- error = assert_raises(URLcrypt::DecryptError) do
41
- ::URLcrypt::decrypt("just some plaintext")
42
- end
43
- assert_equal error.message, "not a valid string to decrypt"
27
+ def test_key_deprecation
28
+ URLcrypt.key = 'aaaa'
29
+ assert_equal "\xAA\xAA", ENV.fetch('urlcrypt_key')
44
30
  end
45
31
  end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ require 'test_helper'
3
+
4
+ class URLcryptEncryptionTest < TestClass
5
+ def teardown
6
+ ENV["urlcrypt_key"] = nil
7
+ end
8
+
9
+ def test_requires_ENV_if_no_key_provided
10
+ error = assert_raises(KeyError) do
11
+ ::URLcrypt::decrypt("just some plaintext")
12
+ end
13
+ assert_equal error.message, "key not found: \"urlcrypt_key\""
14
+
15
+ error = assert_raises(KeyError) do
16
+ ::URLcrypt::encrypt("just some plaintext")
17
+ end
18
+ assert_equal error.message, "key not found: \"urlcrypt_key\""
19
+ end
20
+
21
+ def test_encryption_with_ENV_key
22
+ # pack() converts this secret into a byte array
23
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
24
+ ENV['urlcrypt_key'] = secret
25
+
26
+ assert_equal OpenSSL::Cipher.new('aes-256-cbc').key_len, secret.bytesize
27
+
28
+ original = "hello world!"
29
+ encrypted = URLcrypt::encrypt(original)
30
+ assert_equal(URLcrypt::decrypt(encrypted), original)
31
+ end
32
+
33
+ def test_decrypt_error_with_ENV_key
34
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
35
+ ENV['urlcrypt_key'] = secret
36
+ error = assert_raises(URLcrypt::DecryptError) do
37
+ ::URLcrypt::decrypt("just some plaintext")
38
+ end
39
+ assert_equal error.message, "not a valid string to decrypt"
40
+ end
41
+
42
+ def test_encryption_with_explicit_key
43
+ # pack() converts this secret into a byte array
44
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
45
+
46
+ assert_equal OpenSSL::Cipher.new('aes-256-cbc').key_len, secret.bytesize
47
+
48
+ original = "hello world!"
49
+ encrypted = URLcrypt::encrypt(original, key: secret)
50
+ assert_equal(URLcrypt::decrypt(encrypted, key: secret), original)
51
+ end
52
+
53
+ def test_decrypt_error_with_explicit_key
54
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
55
+ error = assert_raises(URLcrypt::DecryptError) do
56
+ ::URLcrypt::decrypt("just some plaintext", key: secret)
57
+ end
58
+ assert_equal error.message, "not a valid string to decrypt"
59
+ end
60
+
61
+ def test_threads_with_ENV_keys
62
+ # pack() converts this secret into a byte array
63
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
64
+ ENV['urlcrypt_key'] = secret
65
+
66
+ assert_equal OpenSSL::Cipher.new('aes-256-cbc').key_len, secret.bytesize
67
+
68
+ parent_string = "hello world!"
69
+ parent_encrypted = URLcrypt::encrypt(parent_string)
70
+ assert_equal(URLcrypt::decrypt(parent_encrypted), parent_string)
71
+
72
+ threads = 100.times.map do |n|
73
+ Thread.new{
74
+ original = "Test String #{n}"
75
+ encrypted = URLcrypt::encrypt(original)
76
+ assert_equal(URLcrypt::decrypt(encrypted), original)
77
+
78
+ thread_encrypted = URLcrypt::encrypt(parent_string)
79
+
80
+ assert_equal(URLcrypt::decrypt(thread_encrypted), parent_string)
81
+ assert_equal(URLcrypt::decrypt(parent_encrypted), parent_string)
82
+ }
83
+ end
84
+
85
+ threads.each { |thr| thr.join }
86
+ end
87
+
88
+ def test_threads_with_explicit_per_thread_keys
89
+ threads = 100.times.map do |n|
90
+ Thread.new{
91
+ key = ["d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d-secret-key-#{n}"].pack('H*')
92
+ original = "Test String #{n}"
93
+ encrypted = URLcrypt::encrypt(original, key: key)
94
+ assert_equal(URLcrypt::decrypt(encrypted, key: key), original)
95
+ }
96
+ end
97
+
98
+ threads.each { |thr| thr.join }
99
+ end
100
+
101
+ def test_threads_with_explicit_per_thread_keys_overriding_ENV_variable
102
+ secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d'].pack('H*')
103
+ ENV['urlcrypt_key'] = secret
104
+
105
+ threads = 100.times.map do |n|
106
+ Thread.new{
107
+ key = ["d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d-secret-key-#{n}"].pack('H*')
108
+
109
+ original = "Test String #{n}"
110
+ encrypted = URLcrypt::encrypt(original, key: key)
111
+ assert_equal(URLcrypt::decrypt(encrypted, key: key), original)
112
+
113
+ parent_encryption = URLcrypt::encrypt(original)
114
+ refute_equal parent_encryption, encrypted
115
+ }
116
+ end
117
+
118
+ threads.each { |thr| thr.join }
119
+ end
120
+ end
@@ -1,36 +1,31 @@
1
1
  # encoding: utf-8
2
2
  class URLcryptRegressionTest < TestClass
3
+ def setup
4
+ # this key was generated via rake secret in a rails app, the pack() converts it into a byte array
5
+ @secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d721968887a19bdb01af8f59eb5a90d256bd9903355c20b0b4b39bf4048b9b17b'].pack('H*')
6
+ end
7
+
3
8
  def test_encryption_and_decryption
4
9
  original = '{"some":"json_data","token":"dfsfsdfsdf"}'
5
- encrypted = URLcrypt.encrypt(original)
10
+ encrypted = URLcrypt.encrypt(original, key: @secret)
6
11
 
7
- encrypted = URLcrypt::encrypt(original)
8
- assert_equal(URLcrypt::decrypt(encrypted), original)
12
+ assert_equal(URLcrypt::decrypt(encrypted, key: @secret), original)
9
13
  end
10
14
 
11
15
  def test_encryption_with_too_long_key
12
- # this key was generated via rake secret in a rails app, the pack() converts it into a byte array
13
- secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d721968887a19bdb01af8f59eb5a90d256bd9903355c20b0b4b39bf4048b9b17b'].pack('H*')
14
- URLcrypt::key = secret
15
-
16
- assert OpenSSL::Cipher.new('aes-256-cbc').key_len < secret.bytesize
16
+ assert OpenSSL::Cipher.new('aes-256-cbc').key_len < @secret.bytesize
17
17
 
18
18
  original = "hello world!"
19
- encrypted = URLcrypt::encrypt(original)
20
- assert_equal(URLcrypt::decrypt(encrypted), original)
19
+ encrypted = URLcrypt::encrypt(original, key: @secret)
20
+ assert_equal(URLcrypt::decrypt(encrypted, key: @secret), original)
21
21
  end
22
22
 
23
23
  def test_encryption_and_decryption_with_too_long_key
24
- # this key was generated via rake secret in a rails app, the pack() converts it into a byte array
25
- secret = ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d721968887a19bdb01af8f59eb5a90d256bd9903355c20b0b4b39bf4048b9b17b'].pack('H*')
26
- URLcrypt::key = secret
27
-
28
- assert OpenSSL::Cipher.new('aes-256-cbc').key_len < secret.bytesize
24
+ assert OpenSSL::Cipher.new('aes-256-cbc').key_len < @secret.bytesize
29
25
 
30
26
  original = '{"some":"json_data","token":"dfsfsdfsdf"}'
31
- encrypted = URLcrypt.encrypt(original)
27
+ encrypted = URLcrypt.encrypt(original, key: @secret)
32
28
 
33
- encrypted = URLcrypt::encrypt(original)
34
- assert_equal(URLcrypt::decrypt(encrypted), original)
29
+ assert_equal(URLcrypt::decrypt(encrypted, key: @secret), original)
35
30
  end
36
31
  end
data/test/test_helper.rb CHANGED
@@ -2,12 +2,12 @@
2
2
  require 'bundler'
3
3
  Bundler.require(:default, :test)
4
4
 
5
- require 'coveralls'
6
- Coveralls.wear!
5
+ require 'simplecov'
6
+ SimpleCov.start
7
7
 
8
- require 'test/unit'
8
+ require "minitest/autorun"
9
9
 
10
- class TestClass < Test::Unit::TestCase
10
+ class TestClass < Minitest::Test
11
11
  require 'URLcrypt'
12
12
 
13
13
  def assert_bytes_equal(string1, string2)
@@ -15,7 +15,7 @@ class TestClass < Test::Unit::TestCase
15
15
  bytes2 = string2.bytes.to_a.join(':')
16
16
  assert_equal(bytes1, bytes2)
17
17
  end
18
-
18
+
19
19
  def assert_decoding(encoded, plain)
20
20
  decoded = URLcrypt.decode(encoded)
21
21
  assert_bytes_equal(plain, decoded)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: urlcrypt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Fuchs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-02 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: thomas@slash7.com
@@ -22,6 +22,7 @@ files:
22
22
  - config/environment.rb
23
23
  - lib/URLcrypt.rb
24
24
  - test/URLcrypt_test.rb
25
+ - test/encryption_test.rb
25
26
  - test/regression_test.rb
26
27
  - test/test_helper.rb
27
28
  homepage:
@@ -44,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
45
  version: '0'
45
46
  requirements:
46
47
  - none
47
- rubygems_version: 3.1.2
48
+ rubygems_version: 3.1.6
48
49
  signing_key:
49
50
  specification_version: 4
50
51
  summary: Securely encode and decode short pieces of arbitrary binary data in URLs.