human_token 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +8 -0
- data/Guardfile +15 -0
- data/LICENSE.txt +3 -0
- data/README.md +142 -0
- data/Rakefile +10 -0
- data/human_token.gemspec +25 -0
- data/lib/human_token.rb +60 -0
- data/lib/human_token/version.rb +3 -0
- data/test/test_human_token.rb +80 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5bd2c6de633f5bcebaea72ae7eac1505ac9739cf
|
4
|
+
data.tar.gz: f92d52708f9964826317111ef016158d89311ecb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 93353224a2123151548107066765c8bd69af4d9400a3ef6276fb886f829d95eb30dcf86219fdd81e75eaf6622573d2f0ac8e8dfe8b4237940b611461f5488fa5
|
7
|
+
data.tar.gz: e5d8e28b1539d023f9a8846a565ef0d0da7267cdc464e0b65eea9d91da6ffa854983ad21c56ee8c340927a200304f2d1fbf09ab3cb7786e521ea30bd0eafd088
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard :bundler do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if your Gemfile contains the `gemspec' command.
|
7
|
+
watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard :minitest do
|
11
|
+
# with Minitest::Unit
|
12
|
+
watch(%r{^test/(.*)\/?test_(.*)\.rb$})
|
13
|
+
watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
14
|
+
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,3 @@
|
|
1
|
+
HumanToken is dedicated to the public domain by its author, Brian Hempel. No rights are reserved. No restrictions are placed on the use of HumanToken. That freedom also means, of course, that no warrenty of fitness is claimed; use HumanToken at your own risk.
|
2
|
+
|
3
|
+
Public domain dedication is explained by the CC0 1.0 summary (and only the summary) at https://creativecommons.org/publicdomain/zero/1.0/
|
data/README.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# HumanToken
|
2
|
+
|
3
|
+
HumanToken is a (relatively) human-friendly token generator. You can create tokens of a given cryptographic strength but without ambiguous characters.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'human_token'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ gem install human_token
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
HumanToken uses Ruby's SecureRandom to generate tokens.
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
require 'human_token'
|
31
|
+
|
32
|
+
HumanToken.generate # => "2re9y4mdsh39jy4qqh3eq6tzptz"
|
33
|
+
```
|
34
|
+
|
35
|
+
Just like SecureRandom, the default is 16 bytes of randomness. However, the token is longer than 16 characters because, well, those 16 bytes aren't binary anymore.
|
36
|
+
|
37
|
+
You can specify an alternate number of bytes of randomness.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# 64-bit token
|
41
|
+
HumanToken.generate(8) # => "nte4mh95kvxdde"
|
42
|
+
```
|
43
|
+
|
44
|
+
By default, tokens contain lowercase alphanumeric characters, with the exceptions of `0` `1` `i` `l` `o` `u`. Those characters are excluded to prevent ambiguity. The `i` and `o` are not ambiguous when lowercase, but are excluded anyway so the token can be treated case-insensitively: uppercase `I` and `O` are ambiguous.
|
45
|
+
|
46
|
+
The letter `u` is excluded because, if it were included, when generating 128 bit tokens about 1 in every 340 tokens would phonetically drop the f-bomb on the viewer—or even more often (a lot more often!) if we count a "u" proceed by an "f". [Douglas Crockford](http://www.crockford.com/wrmg/base32.html) is the inspiration for excluding "u", though he didn't enumerate the math.
|
47
|
+
|
48
|
+
The default tokens are lowercase because lowercase is much easier to read.
|
49
|
+
|
50
|
+
## Schemes
|
51
|
+
|
52
|
+
Several encoding schemes are provided.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
HumanToken.hex # Lowercase hexadecimal (included for comparison purposes)
|
56
|
+
HumanToken.base_30 # Lowercase base 30, no 0 1 I L O U (this is the default scheme)
|
57
|
+
HumanToken.base_31 # Lowercase base 31, no 0 1 I L O
|
58
|
+
HumanToken.base_32 # Crockford's Base 32: Uppercase, no I L O U
|
59
|
+
HumanToken.base_58 # Bitcoin Base 58: Mixed case, no 0 I O l
|
60
|
+
HumanToken.new_base_60 # Tantek Çelik's "New Base 60": Mixed case and underscore, no I O l
|
61
|
+
HumanToken.base_62 # All 62 mixed case alphanumerics
|
62
|
+
```
|
63
|
+
|
64
|
+
You can provide a custom scheme.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
HumanToken.generate(6, characters: "aeiou")
|
68
|
+
# => "iauioieuoaeeeauiauuii"
|
69
|
+
```
|
70
|
+
|
71
|
+
Internally, HumanToken uses [BaseX](https://github.com/brianhempel/base_x) for encoding. A custom scheme can also be specified by providing a BaseX object.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
HumanToken.generate(32, base: BaseX.base(36))
|
75
|
+
# => "9KV0FL722DDYQ5IOFIJDB1DT0AIDUID6U0VUBCRC9IK7POQG9S"
|
76
|
+
```
|
77
|
+
|
78
|
+
For reference, in IRB you can get a list of all the provided schemes with a sample 128 bit token in each.
|
79
|
+
|
80
|
+
```
|
81
|
+
> HumanToken.samples
|
82
|
+
hex "2c50e3ec571d5d662580e22de852147e"
|
83
|
+
base_30 "fwkt9zvwn4ara5n6qfhbmbsvfgr"
|
84
|
+
base_31 "7577vd28g58d8s5g84sgc5c6zq"
|
85
|
+
base_32 "BSGTEN21DRYMDSG75TDEMDDMKS"
|
86
|
+
base_58 "jW9NHFsBv4b2ynHaMa68zy"
|
87
|
+
new_base_60 "Fh7HDfRZ_pGwbUMtVF2ttv"
|
88
|
+
base_62 "x8SuXOLhXSjAx1jGzSCvzB"
|
89
|
+
|
90
|
+
# You can also ask for sample tokens of a given size
|
91
|
+
> HumanToken.samples(4)
|
92
|
+
hex "0a91b59b"
|
93
|
+
base_30 "cx85466"
|
94
|
+
base_31 "un7hxe6"
|
95
|
+
base_32 "N9YXAZ3"
|
96
|
+
base_58 "pJKN11"
|
97
|
+
new_base_60 "wJ5cc_"
|
98
|
+
base_62 "DugJc6"
|
99
|
+
```
|
100
|
+
|
101
|
+
## Other Tidbits
|
102
|
+
|
103
|
+
If you ask for 128 bits (16 bytes) of randomness, you are actually getting _at least_ 128 bits. Why?
|
104
|
+
|
105
|
+
Consider the default generator. There are thirty "numerals" used in the default scheme (the 36 alphanumerics, minus 0, 1, I, O, L, and U). Each "numeral" in base 30 encodes about 4.9 bits of information. A token of length 26 can encdoe 127.6 bits of information. That's not enough for 128 bits of randomness. A token of length 27 can encode 132.5 bits of information. Why waste the extra space by encoding only 128 bits in a string that can encode 132.5? Therefore, HumanToken encodes 132.5 bits of randomness.
|
106
|
+
|
107
|
+
However, if you don't want to explain to your colleagues why your tokens have "132 bit security" instead of a standard number like 128, then you can ask for exactly 128 bits. Your tokens will be the same length, but the first character will appear less random.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
HumanToken.generate(exact_bytes: 16)
|
111
|
+
# => "29spx4xsse3cqgr7da7nrasxfc2"
|
112
|
+
|
113
|
+
# First character will always be a 2 or 3 in this case
|
114
|
+
> 10.times { p HumanToken.generate(exact_bytes: 16) }
|
115
|
+
"2va3np2rhc8phqcwfmyy8mm62b6"
|
116
|
+
"2nhdf3jxz87qcfx4w2j8gna3f8k"
|
117
|
+
"25x5j7eh7k8vfnm372sbayd3brh"
|
118
|
+
"2wtxf5mrcw7aaw2hwk8ybpqex4r"
|
119
|
+
"2at5knh6aw38a8xaxcr262gnxs4"
|
120
|
+
"2bn7a7fx2gq6g8t5cg9yff6qeh8"
|
121
|
+
"23cy29jsbmj4w3xj35f6dygrvqp"
|
122
|
+
"2575jtkajbtyhx44gksdykjcfsq"
|
123
|
+
"29ny2f956jzytxwbt56y2vsea8t"
|
124
|
+
"2yrptgvaq6hyywanssgqm6ca6jb"
|
125
|
+
```
|
126
|
+
|
127
|
+
## License
|
128
|
+
|
129
|
+
Public Domain; no rights reserved.
|
130
|
+
|
131
|
+
No restrictions are placed on the use of HumanToken. That freedom also means, of course, that no warrenty of fitness is claimed; use HumanToken at your own risk.
|
132
|
+
|
133
|
+
Public domain dedication is explained by the CC0 1.0 summary (and only the summary) at https://creativecommons.org/publicdomain/zero/1.0/
|
134
|
+
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
1. Fork it ( http://github.com/brianhempel/human_token/fork )
|
139
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
140
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
141
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
142
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/human_token.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'human_token/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "human_token"
|
8
|
+
spec.version = HumanToken::VERSION
|
9
|
+
spec.authors = ["Brian Hempel"]
|
10
|
+
spec.email = ["plasticchicken@gmail.com"]
|
11
|
+
spec.summary = %q{Tokens for humans: no ambiguous characters! Highly configurable.}
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = "https://github.com/brianhempel/human_token"
|
14
|
+
spec.license = "Public Domain"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "base_x", "~> 0.8.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
data/lib/human_token.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'human_token/version'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'base_x'
|
4
|
+
|
5
|
+
module HumanToken
|
6
|
+
DEFAULTS = { bytes: 16, base: BaseX::Base30L }
|
7
|
+
BASES = [
|
8
|
+
[:hex, BaseX::Base16L],
|
9
|
+
[:base_30, BaseX::Base30L],
|
10
|
+
[:base_31, BaseX::Base31L],
|
11
|
+
[:base_32, BaseX::CrockfordBase32],
|
12
|
+
[:base_58, BaseX::BitcoinBase58],
|
13
|
+
[:new_base_60, BaseX::NewBase60],
|
14
|
+
[:base_62, BaseX::Base62DUL],
|
15
|
+
]
|
16
|
+
|
17
|
+
def self.generate(*args)
|
18
|
+
options = normalize_generate_options(args, DEFAULTS)
|
19
|
+
base, bytes, exact_bytes = options[:base], options[:bytes], options[:exact_bytes]
|
20
|
+
|
21
|
+
base = BaseX.new(options[:characters]) if options[:characters]
|
22
|
+
|
23
|
+
if exact_bytes
|
24
|
+
base.encode(SecureRandom.random_bytes(exact_bytes))
|
25
|
+
else
|
26
|
+
length = (Math.log(256)/Math.log(base.base)*bytes).ceil
|
27
|
+
token = base.encode(SecureRandom.random_bytes(bytes + 1))
|
28
|
+
token[-length..-1]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
BASES.each do |name, base|
|
33
|
+
define_singleton_method(name) do |*args|
|
34
|
+
options = normalize_generate_options(args, base: base)
|
35
|
+
generate(options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.samples(*args)
|
40
|
+
STDERR.puts samples_string(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
private
|
45
|
+
|
46
|
+
def normalize_generate_options(args, defaults)
|
47
|
+
defaults.dup.tap do |options|
|
48
|
+
options[:bytes] = args.first if args.first.is_a?(Integer)
|
49
|
+
args.grep(Hash).each {|hash| options.merge!(hash)}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def samples_string(*args)
|
54
|
+
longest_name_length = BASES.sort_by {|name, base| -name.size}.first[0].size
|
55
|
+
BASES.map do |method_name, _|
|
56
|
+
"%-#{longest_name_length}s %s" % [method_name, self.send(method_name, *args).inspect]
|
57
|
+
end.join("\n")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.require(:test)
|
3
|
+
|
4
|
+
require 'human_token'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
require 'minitest/reporters'
|
7
|
+
MiniTest::Reporters.use! Minitest::Reporters::DefaultReporter.new
|
8
|
+
|
9
|
+
class TestHumanToken < Minitest::Test
|
10
|
+
def test_generate
|
11
|
+
token = HumanToken.generate
|
12
|
+
assert_equal 27, token.length
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_generate_with_bytes_given
|
16
|
+
token = HumanToken.generate(8)
|
17
|
+
assert_equal 14, token.length
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_generate_with_exact_bytes_given
|
21
|
+
token = HumanToken.generate(exact_bytes: 8)
|
22
|
+
assert_equal 14, token.length
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_generate_with_different_base
|
26
|
+
token = HumanToken.generate(base: BaseX::Base58)
|
27
|
+
assert_equal 22, token.length
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_floating_point_math_works
|
31
|
+
token = HumanToken.generate(5, base: BaseX.base(32))
|
32
|
+
assert_equal 8, token.length
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_custom_characters
|
36
|
+
token = HumanToken.generate(6, characters: "asdfjkl;")
|
37
|
+
assert_equal 16, token.length
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_base_30
|
41
|
+
token = HumanToken.base_30
|
42
|
+
assert_equal 27, token.length
|
43
|
+
token = HumanToken.base_30(exact_bytes: 32)
|
44
|
+
assert_equal 53, token.length
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_base_31
|
48
|
+
token = HumanToken.base_31
|
49
|
+
assert_equal 26, token.length
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_base_32
|
53
|
+
token = HumanToken.base_32
|
54
|
+
assert_equal 26, token.length
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_base_58
|
58
|
+
token = HumanToken.base_58
|
59
|
+
assert_equal 22, token.length
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_new_base_60
|
63
|
+
token = HumanToken.new_base_60
|
64
|
+
assert_equal 22, token.length
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_base_62
|
68
|
+
token = HumanToken.base_62
|
69
|
+
assert_equal 22, token.length
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_hex
|
73
|
+
token = HumanToken.hex
|
74
|
+
assert_equal 32, token.length
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_samples
|
78
|
+
HumanToken.send(:samples_string) # doesn't error
|
79
|
+
end
|
80
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: human_token
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Hempel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: base_x
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: 'Tokens for humans: no ambiguous characters! Highly configurable.'
|
56
|
+
email:
|
57
|
+
- plasticchicken@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Guardfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- human_token.gemspec
|
70
|
+
- lib/human_token.rb
|
71
|
+
- lib/human_token/version.rb
|
72
|
+
- test/test_human_token.rb
|
73
|
+
homepage: https://github.com/brianhempel/human_token
|
74
|
+
licenses:
|
75
|
+
- Public Domain
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.2.2
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: 'Tokens for humans: no ambiguous characters! Highly configurable.'
|
97
|
+
test_files:
|
98
|
+
- test/test_human_token.rb
|