human_readable 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5307eeacfd4961a43325a641b37da77388aedcb419f1c2bd0ae1021eec63417b
4
+ data.tar.gz: 44414551a3e4333ab02beb913c69bf8afddce406b2e1701c2f7d79e1150a464d
5
+ SHA512:
6
+ metadata.gz: 1662e91b1671caf3ac01da210016eb0544d6b82a9eec0f93279eaa9460472bea09fa8ba078d5204551dbb948c81c0a9f874a62d4001c41cfe9cd8c7c2dda38a0
7
+ data.tar.gz: b728158418c8b4ea875dc80cccc4055107fbf48014821c325b8e76dfbe6c63f38394d57544c8f13909c376e47f45b19e812fc3ca100994ca2a0a42a2d12eaef9
@@ -0,0 +1,5 @@
1
+ Release History
2
+ ===============
3
+
4
+ # 0.8.0
5
+ * Initial release
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Mack Earnhardt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ # HumanReadable
2
+
3
+ Human readable random tokens with limited ambiguous characters.
4
+
5
+ Focus is readability in poor conditions or from potentially damaged printed documents rather than cryptographic uses.
6
+ Despite this focus, SecureRandom is used to help avoid collisions.
7
+
8
+ Inspired by Douglas Crockford's [Base 32](https://www.crockford.com/base32.html), but attempts to correct mistakes by substituting the most likely misread.
9
+ To make substitution safer, the token includes a check character generated using the [Luhn mod N algorithm](https://en.wikipedia.org/wiki/Luhn_mod_N_algorithm).
10
+ Default character set is all caps based on this published study on [text legibility](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2016788/), which matches Crockford as well.
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'human_readable'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle install
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install human_readable
27
+
28
+ ## Usage
29
+
30
+ For 10 characters of the default character set, use `HumanReadable.generate`.
31
+ For other lengths (2..x), use `HumanReadable.generate(output_size: 50)`.
32
+
33
+ ## Configuration
34
+
35
+ * Change available characters and substitution by manipulating `substitution_hash`
36
+ * To include non-default characters, add a self-reference to the hash
37
+ * Inspect available characters using `HumanReadable.charset`
38
+ * For convenience, numbers and symbols are allowed in the hash and are translated to characters during usage
39
+
40
+ **CAUTION:** Changing `substitution_hash` keys alters the check character, invalidating previous tokens.
41
+
42
+
43
+ HumanReadable.configure do |c|
44
+ # Default: substitution_hash = { I: 1, L: 1, O: 0, U: :V }
45
+
46
+ # Modifications
47
+ c.substitution_hash[:B] = 8
48
+ c.substitution_hash[:U] = nil
49
+ c.substitution_hash['$'] = '$'
50
+ # or equivalently
51
+ c.substitution_hash = { I: 1, L: 1, O: 0, U: nil, B: 8, '$' => '$'}
52
+ end
53
+
54
+ ## Development
55
+
56
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
57
+
58
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
59
+
60
+ ## Contributing
61
+
62
+ Bug reports and pull requests are welcome on GitHub at https://github.com/MacksMind/human_readable.
63
+
64
+
65
+ ## License
66
+
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 Mack Earnhardt
4
+
5
+ require 'human_readable/version'
6
+ require 'ostruct'
7
+ require 'securerandom'
8
+
9
+ # Human readable random tokens with no ambiguous characters
10
+ module HumanReadable
11
+ # +#generate+ output_size must be >= 2 due to check character
12
+ class MinSizeTwo < StandardError; end
13
+
14
+ class << self
15
+ # Yields block for configuration
16
+ #
17
+ # HumanReadable.configure do |c|
18
+ # c.substitution_hash[:B] = 8
19
+ # c.substitution_hash[:U] = nil
20
+ # c.substitution_hash['$'] = '$'
21
+ # # or equivalently
22
+ # c.substitution_hash = { I: 1, L: 1, O: 0, U: nil, B: 8, '$' => '$'}
23
+ # end
24
+ #
25
+ # DEFAULT:
26
+ # substitution_hash: { I: 1, L: 1, O: 0, U: :V }
27
+ #
28
+ # Specified keys won't be used during generation, and values will be substituted during
29
+ # validation, increasing the likelihood that a misread character can be restored. Extend
30
+ # or replace the substitutions to use a different character set. For convenience, numbers
31
+ # and symbols are allowed in the hash and are translated to characters during usage.
32
+ # Alter as needed per examples below.
33
+ #
34
+ # *CAUTION:* Changing substitution_hash keys alters the check character, invalidating previous tokens.
35
+ def configure
36
+ yield(configuration)
37
+ end
38
+
39
+ # Generates a random token of the requested size
40
+ #
41
+ # Minimum size is 2 since the last character is a check character
42
+ def generate(output_size: 10)
43
+ raise(MinSizeTwo) if output_size < 2
44
+
45
+ (token = generate_random(output_size - 1)) + check_character(token)
46
+ end
47
+
48
+ # Clean and validate a candidate token
49
+ #
50
+ # * Upcases
51
+ # * Applies substitutions
52
+ # * Remove characters not in available character set
53
+ # * Validates the check character
54
+ #
55
+ # Return value: Valid token or nil
56
+ def valid_token?(input)
57
+ return unless input.is_a?(String)
58
+
59
+ codepoints = input.upcase.tr(trans_from, trans_to).chars.map! { |c| charset.index(c) }
60
+ codepoints.compact!
61
+
62
+ return if codepoints.size < 2
63
+
64
+ array =
65
+ codepoints.reverse.each_with_index.map do |codepoint, i|
66
+ codepoint *= 2 if i.odd?
67
+ codepoint / charset_size + codepoint % charset_size
68
+ end
69
+
70
+ codepoints.map { |codepoint| charset[codepoint] }.join if (array.sum % charset_size).zero?
71
+ end
72
+
73
+ # Characters available for token generation
74
+ #
75
+ # Manipulate by configuring +substitution_hash+
76
+ #
77
+ # DEFAULT: All number and uppercase letters except for ILOU
78
+ def charset
79
+ @charset ||= (('0'..'9').to_a + ('A'..'Z').to_a - trans_from.chars + trans_to.chars - nil_substitutions).uniq
80
+ end
81
+
82
+ private
83
+
84
+ def configuration
85
+ @configuration ||= OpenStruct.new(
86
+ substitution_hash: { I: 1, L: 1, O: 0, U: :V }
87
+ )
88
+ end
89
+
90
+ # Generates a random string of the requested length from the charset
91
+ #
92
+ # We could use one of the below routines in +#generate+, but the first
93
+ # increases the chances of token collisions and the second is too slow.
94
+ #
95
+ # Array.new(random_size) { charset.sample }
96
+ # # or
97
+ # Array.new(random_size) { charset.sample(random: SecureRandom) }
98
+ #
99
+ # Instead we attempt to optimize the number of bytes generated with each
100
+ # call to SecureRandom.
101
+ def generate_random(random_size)
102
+ return '' unless random_size.positive?
103
+
104
+ codepoints = []
105
+
106
+ while codepoints.size < random_size
107
+ bytes_needed = ((random_size - codepoints.size) * byte_multiplier).ceil
108
+
109
+ codepoints +=
110
+ begin
111
+ array =
112
+ SecureRandom
113
+ .random_bytes(bytes_needed)
114
+ .unpack1('B*')
115
+ .scan(scan_regexp)
116
+ .map! { |bin_string| bin_string.to_i(2) }
117
+ array.select { |codepoint| codepoint < charset_size }
118
+ end
119
+ end
120
+
121
+ codepoints[0, random_size].map { |codepoint| charset[codepoint] }.join
122
+ end
123
+
124
+ # Compute check character using Luhn mod N algorithm
125
+ #
126
+ # *CAUTION:* Changing substitution_hash keys alters the output
127
+ def check_character(input)
128
+ array =
129
+ input.chars.reverse.each_with_index.map do |c, i|
130
+ d = charset.index(c)
131
+ d *= 2 if i.even?
132
+ d / charset_size + d % charset_size
133
+ end
134
+
135
+ mod = (charset_size - array.sum % charset_size) % charset_size
136
+
137
+ charset[mod]
138
+ end
139
+
140
+ def char_bits
141
+ @char_bits ||= (charset_size - 1).to_s(2).size
142
+ end
143
+
144
+ def scan_regexp
145
+ @scan_regexp ||= /.{#{char_bits}}/
146
+ end
147
+
148
+ def byte_multiplier
149
+ @byte_multiplier ||=
150
+ begin
151
+ bit_multiplier = char_bits / 8.0
152
+ # Then extra 1.1 helps performance due to randomness of misses
153
+ miss_percentage = 2**char_bits * 1.0 / charset_size * 1.1
154
+ bit_multiplier * miss_percentage
155
+ end
156
+ end
157
+
158
+ def charset_size
159
+ @charset_size ||= charset.size
160
+ end
161
+
162
+ def nil_substitutions
163
+ @nil_substitutions ||=
164
+ begin
165
+ array = configuration.substitution_hash.each.map { |k, v| k if v.nil? }
166
+ array.compact!
167
+ array.map!(&:to_s)
168
+ array.map!(&:upcase)
169
+ end
170
+ end
171
+
172
+ def trans_from
173
+ @trans_from ||=
174
+ begin
175
+ array = configuration.substitution_hash.each.map { |k, v| k unless v.nil? }
176
+ array.compact!
177
+ array.map!(&:to_s)
178
+ array.map!(&:upcase)
179
+ array.join
180
+ end
181
+ end
182
+
183
+ def trans_to
184
+ @trans_to ||=
185
+ begin
186
+ array = configuration.substitution_hash.values
187
+ array.compact!
188
+ array.map!(&:to_s)
189
+ array.map!(&:upcase)
190
+ array.join
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2020 Mack Earnhardt
4
+
5
+ module HumanReadable
6
+ # Gem version
7
+ VERSION = '0.8.0'
8
+ public_constant :VERSION
9
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: human_readable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Mack Earnhardt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Human readable random tokens with no ambiguous characters
15
+
16
+ Tranlates invalid characters to their most likely original value
17
+ and validates using a checksum.
18
+ email:
19
+ - mack@agilereasoning.com
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - "./CHANGELOG.md"
25
+ - "./LICENSE.txt"
26
+ - "./README.md"
27
+ - lib/human_readable.rb
28
+ - lib/human_readable/version.rb
29
+ homepage: https://github.com/MacksMind/human_readable
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ homepage_uri: https://github.com/MacksMind/human_readable
34
+ source_code_uri: https://github.com/MacksMind/human_readable
35
+ changelog_uri: https://github.com/MacksMind/human_readable/blob/master/CHANGELOG.md
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.4.1
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubygems_version: 3.1.2
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Human readable random tokens with no ambiguous characters
55
+ test_files: []