urlcrypt 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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