urlcrypt 0.0.1 → 0.1.0

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.
Files changed (5) hide show
  1. data/README.md +62 -22
  2. data/Rakefile +1 -1
  3. data/lib/URLcrypt.rb +34 -4
  4. data/test/URLcrypt_test.rb +19 -2
  5. metadata +3 -3
data/README.md CHANGED
@@ -1,34 +1,34 @@
1
1
  # URLcrypt
2
2
 
3
+ [![Build Status](https://travis-ci.org/madrobby/URLcrypt.png?branch=master)](https://travis-ci.org/madrobby/URLcrypt)
4
+
3
5
  Ever wanted to securely transmit (not too long) pieces of arbitrary binary data
4
- in a URL? *urlencrypt* makes it easy!
6
+ in a URL? **URLcrypt** makes it easy!
5
7
 
6
- This gem is based on the base32 gem from Samuel Tesla.
8
+ This gem is based on the [base32](https://github.com/stesla/base32) gem from Samuel Tesla.
7
9
 
8
- URLcrypt uses a 256-bit AES symmetric encryption to securely encrypt data, and
9
- and encodes and decodes Base 62 strings that can be used directly in URLs.
10
+ URLcrypt uses **256-bit AES symmetric encryption**
11
+ to securely encrypt data, and encodes and decodes
12
+ **Base 32 strings that can be used directly in URLs**.
10
13
 
11
- For example, this can be used to securely store user ids and the like when you
12
- access a web application from a place that doesn't have other authentication
13
- mechanisms, like when you load an image in an email.
14
+ This can be used to securely store user ids, download expiration dates and
15
+ other arbitrary data like that when you access a web application from a place
16
+ that doesn't have other authentication or persistence mechanisms (like cookies):
17
+
18
+ * Loading a generated image from your web app in an email
19
+ * Links that come with an expiration date (à la S3)
20
+ * Mini-apps that don't persist data on the server
14
21
 
15
- URLcrypt uses a modified Base 32 algorithm that doesn't use padding characters,
16
- and doesn't use vowels to avoid bad words in the generated string.
17
-
18
- The main reason why Base 32 is useful is that Ruby's built-in Base 64 support
19
- is, IMO, looking ugly in URLs and requires several characters that need to be
20
- URL-escaped.
21
-
22
- Unfortunately, some other gems out there that in theory could handle this
23
- (like the radix gem) fail with strings that start with a "\0" byte.
22
+ Works with Ruby 1.8, 1.9 and 2.0.
24
23
 
25
- *WORD OF WARNING* THERE IS NO GUARANTEE WHATSOEVER THAT THIS GEM IS ACTUALLY
26
- SECURE AND WORKS. USE AT YOUR OWN RISK.
24
+ **Important**: As a general guideline, URL lengths shouldn't exceed about 2000
25
+ characters in length, as URLs longer than that will not work in some browsers
26
+ and with some (proxy) servers. This limits the amount of data you can store
27
+ with URLcrypt.
27
28
 
28
- Note: this is version 0.0.1 which doesn't actually come with the encryption part
29
- just yet. It will only work on Ruby 1.8.7 for now.
29
+ **WORD OF WARNING: THERE IS NO GUARANTEE WHATSOEVER THAT THIS GEM IS ACTUALLY SECURE AND WORKS. USE AT YOUR OWN RISK.**
30
30
 
31
- Patches are welcome, please include tests.
31
+ Patches are welcome; please include tests!
32
32
 
33
33
  ## Installation
34
34
 
@@ -37,16 +37,56 @@ Add the `urlcrypt` gem to your Gemfile.
37
37
  ## Simple Example
38
38
 
39
39
  ```ruby
40
+ # encrypt and encode with 256-bit AES
41
+ # one-time setup, set this to a securely random key with at least 256 bits, see below
42
+ URLcrypt::key = '...'
43
+
44
+ # now encrypt and decrypt!
45
+ URLcrypt::encrypt('chunky bacon!') # => "sgmt40kbmnh1663nvwknxk5l0mZ6Av2ndhgw80rkypnp17xmmg5hy"
46
+ URLcrypt::decrypt('sgmt40kbmnh1663nvwknxk5l0mZ6Av2ndhgw80rkypnp17xmmg5hy')
47
+ # => "chunky bacon!"
48
+
49
+ # encoding without encryption (don't use for anything sensitive!), doesn't need key set
40
50
  URLcrypt.encode('chunky bacon!') # => "mnAhk6tlp2qg2yldn8xcc"
41
51
  URLcrypt.decode('mnAhk6tlp2qg2yldn8xcc') # => "chunky bacon!"
42
52
  ```
43
53
 
54
+ ### Generating keys
55
+
56
+ The easiest way to generate a secure key is to use `rake secret` in a Rails app:
57
+
58
+ ```
59
+ % rake secret
60
+ ba7f56f8f9873b1653d7f032cc474938fd749ee8fbbf731a7c41d698826aca3cebfffa832be7e6bc16eaddc3826602f35d3fd6b185f261ee8b0f01d33adfbe64
61
+ ```
62
+
63
+ To use the key with URLcrypt, you'll need to convert that from a hex string into a real byte array:
64
+
65
+ ```
66
+ URLcrypt::key = ['longhexkeygoeshere'].pack('H*')
67
+ ```
68
+
44
69
  ## Running the Test Suite
45
70
 
46
71
  If you want to run the automated tests for URLcrypt, issue this command from the
47
72
  distribution directory.
48
73
 
49
- % rake test:all
74
+ ```
75
+ % rake test:all
76
+ ```
77
+
78
+ ## Why not Base 64, or an other radix/base library?
79
+
80
+ URLcrypt uses a modified Base 32 algorithm that doesn't use padding characters,
81
+ and doesn't use vowels to avoid bad words in the generated string.
82
+
83
+ The main reason why Base 32 is useful is that Ruby's built-in Base 64 support
84
+ is, IMO, looking ugly in URLs and requires several characters that need to be
85
+ URL-escaped.
86
+
87
+ Unfortunately, some other gems out there that in theory could handle this
88
+ (like the radix gem) fail with strings that start with a "\0" byte.
89
+
50
90
 
51
91
  ## References
52
92
 
data/Rakefile CHANGED
@@ -37,7 +37,7 @@ gemspec = Gem::Specification.new do |s|
37
37
  s.require_paths << 'lib'
38
38
  s.requirements << 'none'
39
39
  s.summary = "Securely encode and decode short pieces of arbitrary binary data in URLs."
40
- s.version = "0.0.1"
40
+ s.version = "0.1.0"
41
41
  end
42
42
 
43
43
  Gem::PackageTask.new(gemspec) do |pkg|
@@ -1,9 +1,15 @@
1
+ require 'openssl'
2
+
1
3
  module URLcrypt
2
4
  # avoid vowels to not generate four-letter words, etc.
3
5
  # this is important because those words can trigger spam
4
6
  # filters when URLs are used in emails
5
7
  TABLE = "1bcd2fgh3jklmn4pqrstAvwxyz567890".freeze
6
8
 
9
+ def self.key=(key)
10
+ @key = key
11
+ end
12
+
7
13
  class Chunk
8
14
  def initialize(bytes)
9
15
  @bytes = bytes
@@ -25,6 +31,7 @@ module URLcrypt
25
31
  [(0..n-1).to_a.reverse.collect {|i| TABLE[(c >> i * 5) & 0x1f].chr},
26
32
  ("=" * (8-n))] # TODO: remove '=' padding generation
27
33
  end
34
+
28
35
  end
29
36
 
30
37
  def self.chunks(str, size)
@@ -38,11 +45,34 @@ module URLcrypt
38
45
  end
39
46
 
40
47
  # strip '=' padding, because we don't need it
41
- def self.encode(str)
42
- chunks(str, 5).collect(&:encode).flatten.join.tr('=','')
48
+ def self.encode(data)
49
+ chunks(data, 5).collect(&:encode).flatten.join.tr('=','')
43
50
  end
44
51
 
45
- def self.decode(str)
46
- chunks(str, 8).collect(&:decode).flatten.join
52
+ def self.decode(data)
53
+ chunks(data, 8).collect(&:decode).flatten.join
54
+ end
55
+
56
+ def self.decrypt(data)
57
+ parts = data.split('Z').map{|part| decode(part)}
58
+ decrypter = cipher(:decrypt)
59
+ decrypter.iv = parts[0]
60
+ decrypter.update(parts[1]) + decrypter.final
47
61
  end
62
+
63
+ def self.encrypt(data)
64
+ crypter = cipher(:encrypt)
65
+ crypter.iv = iv = crypter.random_iv
66
+ "#{encode(iv)}Z#{encode(crypter.update(data) + crypter.final)}"
67
+ end
68
+
69
+ private
70
+
71
+ def self.cipher(mode)
72
+ cipher = OpenSSL::Cipher.new('aes-256-cbc')
73
+ cipher.send(mode)
74
+ cipher.key = @key
75
+ cipher
76
+ end
77
+
48
78
  end
@@ -1,15 +1,22 @@
1
+ # encoding: utf-8
1
2
  require 'test/unit'
2
3
  require 'URLcrypt'
3
4
 
4
5
  class TestURLcrypt < Test::Unit::TestCase
6
+ def assert_bytes_equal(string1, string2)
7
+ bytes1 = string1.bytes.to_a.join(':')
8
+ bytes2 = string2.bytes.to_a.join(':')
9
+ assert_equal(bytes1, bytes2)
10
+ end
11
+
5
12
  def assert_decoding(encoded, plain)
6
13
  decoded = URLcrypt.decode(encoded)
7
- assert_equal(plain, decoded)
14
+ assert_bytes_equal(plain, decoded)
8
15
  end
9
16
 
10
17
  def assert_encoding(encoded, plain)
11
18
  actual = URLcrypt.encode(plain)
12
- assert_equal(encoded, actual)
19
+ assert_bytes_equal(encoded, actual)
13
20
  end
14
21
 
15
22
  def assert_encode_and_decode(encoded, plain)
@@ -38,5 +45,15 @@ class TestURLcrypt < Test::Unit::TestCase
38
45
  assert_decoding(encoded, original)
39
46
  end
40
47
  end
48
+
49
+ def test_encryption
50
+ # this key was generated via rake secret in a rails app, the pack() converts it into a byte array
51
+ URLcrypt::key =
52
+ ['d25883a27b9a639da85ea7e159b661218799c9efa63069fac13a6778c954fb6d721968887a19bdb01af8f59eb5a90d256bd9903355c20b0b4b39bf4048b9b17b'].pack('H*')
53
+
54
+ original = "hello world!"
55
+ encrypted = URLcrypt::encrypt(original)
56
+ assert_equal(URLcrypt::decrypt(encrypted), original)
57
+ end
41
58
 
42
59
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: urlcrypt
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
8
  - 1
10
- version: 0.0.1
9
+ - 0
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Thomas Fuchs