saltpack 0.1.0.pre.20200506181314

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: 00f82f95cf674c68df8092b2bf5c8ec467edebf53bf617874fe5207aa7be30f5
4
+ data.tar.gz: 4567c6a8b231262759793746d3a88b13b27eac5d166cb8441e9b3e32512ada80
5
+ SHA512:
6
+ metadata.gz: f00b2a1e3accb96737555074bdcb6b08186b6b52c623de55ef3b75129ac57a37de4ffe3984e36aaac63c4cc6cdb140a0b4588f5851d3c972bb4d2b71d1daeee0
7
+ data.tar.gz: 1dc2f735ae85c06d9bb2ef42b7364f66c6c0659a08755fd4dc6e501d2574d7a7b77301fe7dbeee2d45f1e12d0b9be308850a0ace6b4d906d7c5d468dc37ab512
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ README.md
3
+ ChangeLog.md
4
+
5
+ LICENSE.txt
@@ -0,0 +1,16 @@
1
+ --- !ruby/object:RDoc::Options
2
+ encoding: UTF-8
3
+ static_path: []
4
+ rdoc_include:
5
+ - .
6
+ charset: UTF-8
7
+ exclude:
8
+ hyperlink_all: false
9
+ line_numbers: false
10
+ main_page: README.md
11
+ markup: markdown
12
+ show_hash: false
13
+ tab_width: 8
14
+ title: Saltpack Documentation
15
+ visibility: :protected
16
+ webcvs:
@@ -0,0 +1,9 @@
1
+ # Simplecov config
2
+
3
+ SimpleCov.start do
4
+ add_filter 'spec'
5
+ add_filter 'integration'
6
+ add_group "Needing tests" do |file|
7
+ file.covered_percent < 90
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ 2019-09-10 Michael Granger <ged@FaerieMUD.org>
2
+
3
+ @ * lib/saltpack.rb, lib/saltpack/header.rb, lib/saltpack/message.rb:
4
+ | Start refactoring the Header parts
5
+ | [472adffb8160] [tip]
6
+ |
7
+ 2019-08-05 Michael Granger <ged@FaerieMUD.org>
8
+
9
+ o * Manifest.txt, Rakefile, examples/post_signed_message.rb,
10
+ | lib/saltpack.rb, lib/saltpack/armor.rb, lib/saltpack/header.rb,
11
+ | lib/saltpack/message.rb, spec/data/msg1-ciphertext.txt,
12
+ | spec/data/msg1.txt, spec/saltpack/armor_spec.rb,
13
+ | spec/saltpack_spec.rb, spec/spec_helper.rb:
14
+ | Start splitting out the mega-methods
15
+ | [11c1e2a2a876]
16
+ |
17
+ 2019-07-05 Michael Granger <ged@FaerieMUD.org>
18
+
19
+ o * LICENSE.txt, lib/saltpack.rb, lib/saltpack/header.rb,
20
+ | spec/saltpack_spec.rb:
21
+ | More work on the header and specs
22
+ | [ae40008f4913]
23
+ |
24
+ o * .gems, .ruby-version, README.md, Rakefile, lib/saltpack.rb,
25
+ | lib/saltpack/errors.rb, lib/saltpack/header.rb,
26
+ | lib/saltpack/message.rb, lib/saltpack/payload.rb,
27
+ | lib/saltpack/refinements.rb, spec/saltpack_spec.rb,
28
+ | spec/spec_helper.rb:
29
+ | Checkpoint of work on encrypt/decrypt
30
+ | [93ec1ec832de]
31
+ |
32
+ 2019-07-02 Michael Granger <ged@FaerieMUD.org>
33
+
34
+ o * lib/saltpack/header.rb, spec/saltpack/header_spec.rb:
35
+ | Add work on Header.generate
36
+ | [f36117158674]
37
+ |
38
+ 2018-10-29 Michael Granger <ged@FaerieMUD.org>
39
+
40
+ o * .gems, .hgignore, .ruby-version, README.md, Rakefile,
41
+ | examples/post_signed_message.rb, lib/saltpack.rb,
42
+ | lib/saltpack/errors.rb, lib/saltpack/header.rb,
43
+ | lib/saltpack/recipient.rb, saltpack.gemspec,
44
+ | spec/saltpack/header_spec.rb, spec/saltpack/recipient_spec.rb:
45
+ | Add project files, recipient and header classes
46
+ | [4a279ccbc93b]
47
+ |
48
+ 2018-10-23 Michael Granger <ged@FaerieMUD.org>
49
+
50
+ o * .document, .editorconfig, .gems, .hgignore, .pryrc, .rdoc_options,
51
+ .ruby-gemset, .ruby-version, .simplecov, Gemfile, History.md,
52
+ LICENSE.txt, Manifest.txt, README.md, Rakefile, certs/ged.pem,
53
+ lib/saltpack.rb, spec/saltpack_spec.rb, spec/spec_helper.rb:
54
+ Initial commit.
55
+ [7fbbd4e46123]
@@ -0,0 +1,4 @@
1
+ ## v0.0.1 [YYYY-MM-DD] Michael Granger <ged@FaerieMUD.org>
2
+
3
+ Initial release.
4
+
@@ -0,0 +1,19 @@
1
+ Copyright © 2018-2019 Michael Granger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the “Software”), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,25 @@
1
+ .document
2
+ .rdoc_options
3
+ .simplecov
4
+ ChangeLog
5
+ History.md
6
+ LICENSE.txt
7
+ Manifest.txt
8
+ README.md
9
+ Rakefile
10
+ examples/post_signed_message.rb
11
+ lib/saltpack.rb
12
+ lib/saltpack/armor.rb
13
+ lib/saltpack/errors.rb
14
+ lib/saltpack/header.rb
15
+ lib/saltpack/message.rb
16
+ lib/saltpack/payload.rb
17
+ lib/saltpack/recipient.rb
18
+ lib/saltpack/refinements.rb
19
+ spec/data/msg1-ciphertext.txt
20
+ spec/data/msg1.txt
21
+ spec/saltpack/armor_spec.rb
22
+ spec/saltpack/header_spec.rb
23
+ spec/saltpack/recipient_spec.rb
24
+ spec/saltpack_spec.rb
25
+ spec/spec_helper.rb
@@ -0,0 +1,89 @@
1
+ # Saltpack
2
+
3
+ home
4
+ : https://hg.sr.ht/~ged/Saltpack
5
+
6
+ github
7
+ : https://github.com/ged/saltpack-ruby
8
+
9
+ docs
10
+ : https://deveiate.org/code/saltpack
11
+
12
+
13
+ ## Description
14
+
15
+ A Ruby implementation of Saltpack, a modern crypto messaging format based on Dan Bernstein's [NaCl][].
16
+
17
+ See also: <https://saltpack.org/>
18
+
19
+
20
+ ## Prerequisites
21
+
22
+ * Ruby
23
+
24
+
25
+ ## Installation
26
+
27
+ $ gem install saltpack
28
+
29
+
30
+ ## Contributing
31
+
32
+ You can check out the current development source with Mercurial via its
33
+ [project page][saltpack-ruby]. Or if you prefer Git, via
34
+ [its Github mirror][github-mirror].
35
+
36
+ After checking out the source, run:
37
+
38
+ $ gem install -Ng
39
+ $ rake setup
40
+
41
+ This task will install any missing dependencies and do any necessary developer
42
+ setup.
43
+
44
+
45
+ ## Authors
46
+
47
+ - Michael Granger <ged@faeriemud.org>
48
+
49
+
50
+ ## License
51
+
52
+ Large portions of this library are ported from the [saltpack-python][] library by
53
+ Jack O'Connor &lt;oconnor663+pypi@gmail.com&gt;, used under the terms of the MIT
54
+ License. No license statement is included in the source, but I'm assuming it's
55
+ something like:
56
+
57
+ > Copyright © 2018 Jack O'Connor
58
+ >
59
+ > Permission is hereby granted, free of charge, to any person obtaining
60
+ > a copy of this software and associated documentation files (the
61
+ > “Software”), to deal in the Software without restriction, including
62
+ > without limitation the rights to use, copy, modify, merge, publish,
63
+ > distribute, sublicense, and/or sell copies of the Software, and to
64
+ > permit persons to whom the Software is furnished to do so, subject to
65
+ > the following conditions:
66
+ >
67
+ > The above copyright notice and this permission notice shall be
68
+ > included in all copies or substantial portions of the Software.
69
+ >
70
+ > THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY
71
+ > KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
72
+ > WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
73
+ > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
74
+ > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
75
+ > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
76
+ > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
77
+
78
+ The port and the rest of the code is:
79
+
80
+ Copyright © 2018-2019, Michael Granger
81
+ All rights reserved.
82
+
83
+ And is also distributed under the terms of the MIT license.
84
+
85
+
86
+ [NaCl]: https://nacl.cr.yp.to/
87
+ [saltpack-ruby]: https://hg.sr.ht/~ged/Saltpack
88
+ [github-mirror]: https://github.com/ged/saltpack-ruby
89
+ [saltpack-python]: https://github.com/keybase/saltpack-python
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby -S rake
2
+
3
+ require 'rake/deveiate'
4
+
5
+ Rake::DevEiate.setup( 'saltpack' ) do |project|
6
+ project.publish_to = 'deveiate:/usr/local/www/public/code'
7
+ project.licenses = [ 'MIT' ]
8
+ end
9
+
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbnacl'
4
+ require 'saltpack'
5
+
6
+ key1 = RbNaCl::PrivateKey.
7
+ new( ["1e42fd5f0ba92398e6e87b633d06987467f237caac217693c7ee14056f153b3a"].pack('h*') )
8
+ key2 = RbNaCl::PrivateKey.
9
+ new( ["3d8ff66ec5d042a41fdcc93e57b909647e9017a6599f8120dd1726a4772a0626"].pack('h*') )
10
+
11
+ msg = Saltpack::Message.new( 'Hey, want to get a beer later?', from: key1, to: key2 )
12
+
13
+ msg.add_recipient( key3 )
14
+ # -or-
15
+ msg.add_anonymous_recipient( key3 )
16
+
17
+ puts msg.encrypt
18
+ # -or-
19
+ puts msg.encrypt( armor: true )
20
+
21
+
@@ -0,0 +1,112 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'rbnacl'
5
+ require 'loggability'
6
+
7
+
8
+ # Saltpack -- a modern crypto messaging format based on Dan Bernstein's NaCl.
9
+ #
10
+ # Refs:
11
+ # - https://saltpack.org/
12
+ # - https://nacl.cr.yp.to/
13
+ module Saltpack
14
+ extend Loggability
15
+
16
+ # Package version
17
+ VERSION = '0.0.1'
18
+
19
+ # Version control revision
20
+ REVISION = %q$Revision: e216e8bc10bb $
21
+
22
+ # The default options for the ::encrypt/::decrypt methods.
23
+ DEFAULT_ENCRYPTION_OPTIONS = {
24
+ chunk_size: 10 ** 6,
25
+ visible_recipients: false,
26
+ }
27
+
28
+ # The 32-byte zero string used to create the recipient hashes/MAC keys
29
+ ZEROS_32 = RbNaCl::Util.zeros( 32 )
30
+
31
+
32
+ # Create a logger for this library
33
+ log_as :saltpack
34
+
35
+
36
+ require 'saltpack/errors'
37
+
38
+ autoload :Armor, 'saltpack/armor'
39
+ autoload :Header, 'saltpack/header'
40
+ autoload :Message, 'saltpack/message'
41
+ autoload :Payload, 'saltpack/payload'
42
+ autoload :Recipient, 'saltpack/recipient'
43
+
44
+
45
+ ### Encrypt the given +message+ for the given +recipient_public_keys+ using the
46
+ ### +sender_key+.
47
+ def self::encrypt( message, sender_key, *recipient_public_keys, **options )
48
+ msg = Saltpack::Message.new( message, sender_key, *recipient_public_keys, **options )
49
+ return msg.to_s
50
+ end
51
+
52
+
53
+ ### Decrypt the given +message+ with the specified +recipient_key+.
54
+ def self::decrypt( message, recipient_key )
55
+ msg = Saltpack::Message.read( message, recipient_key )
56
+ return msg.decrypt
57
+ end
58
+
59
+
60
+ ### Return the +input_bytes+ ascii-armored using the specified +options+
61
+ def self::armor( input, **options )
62
+ return Saltpack::Armor.armor( input, **options )
63
+ end
64
+
65
+
66
+ ### Decode the ascii-armored data from the specified +input_chars+ using
67
+ ### the given +options+.
68
+ def self::dearmor( input_chars, **options )
69
+ return Saltpack::Armor.dearmor( input_chars, **options )
70
+ end
71
+
72
+
73
+ #
74
+ # Utility functions
75
+ #
76
+
77
+ ### Calculate a MAC hash for the recipient at the given +index+ given the specified
78
+ ### +header_hash+, and the two keypairs.
79
+ def self::calculate_recipient_hash( header_hash, index, keypair1, keypair2 )
80
+
81
+ # 9. Concatenate the first 16 bytes of the header hash from step 7 above, with the
82
+ # recipient index from step 4 above. This is the basis of each recipient's MAC
83
+ # nonce.
84
+ mac_key_nonce_prefix = header_hash[0, 16]
85
+ basis = mac_key_nonce_prefix + [ index ].pack('Q>')
86
+
87
+ # Clear the least significant bit of byte 15. That is: nonce[15] &= 0xfe.
88
+ nonce1 = basis.dup
89
+ nonce1[15] = (nonce1[15].ord & 0xfe).chr
90
+
91
+ # Modify the nonce from step 10 by setting the least significant bit of byte
92
+ # That is: nonce[15] |= 0x01.
93
+ nonce2 = basis.dup
94
+ nonce2[15] = (nonce2[15].ord | 0x01).chr
95
+
96
+ # Encrypt 32 zero bytes using crypto_box with the recipient's public key, the
97
+ # sender's long-term private key, and the nonce from the previous step.
98
+ # Encrypt 32 zero bytes again, as in step 11, but using the ephemeral private
99
+ # key rather than the sender's long term private key.
100
+ box1 = RbNaCl::Box.new( *keypair1 ).encrypt( nonce1, ZEROS_32 )
101
+ box2 = RbNaCl::Box.new( *keypair2 ).encrypt( nonce2, ZEROS_32 )
102
+
103
+ # Concatenate the last 32 bytes each box from steps 11 and 13. Take the SHA512
104
+ # hash of that concatenation. The recipient's MAC Key is the first 32 bytes of
105
+ # that hash.
106
+ mac_hash = RbNaCl::Hash.sha512( box1[-16..] + box2[-16..] )
107
+
108
+ return mac_hash[ 0, 32 ]
109
+ end
110
+
111
+ end # module Saltpack
112
+
@@ -0,0 +1,211 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'loggability'
5
+
6
+ require 'saltpack' unless defined?( Saltpack )
7
+
8
+
9
+ # Utility functions for armoring and dearmoring.
10
+ module Saltpack::Armor
11
+ extend Loggability
12
+
13
+
14
+ # The Base64 alphabet
15
+ B64ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
16
+
17
+ # The Base62 alphabet
18
+ B62ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
19
+
20
+ # The Base85 alphabet
21
+ B85ALPHABET = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
22
+ "[\\]^_`abcdefghijklmnopqrstu"
23
+
24
+ # The default options used by the ::armor/::dearmor methods.
25
+ DEFAULT_OPTIONS = {
26
+ alphabet: B62ALPHABET,
27
+ block_size: 32,
28
+ char_block_size: 43,
29
+ raw: false,
30
+ shift: false,
31
+ message_type: 'MESSAGE',
32
+ }
33
+
34
+
35
+ # Loggability -- use the saltpack logger
36
+ log_to :saltpack
37
+
38
+
39
+ ### Given a String-like object, return an Enumerator that will yield successive slices
40
+ ### of the specified +chunk_size+.
41
+ def self::slice_enum_for( string, chunk_size )
42
+ string = string.dup.freeze
43
+ stepper = ( 0 .. string.length ).step( chunk_size )
44
+ return stepper.lazy.map {|i| string.slice(i, chunk_size) }
45
+ end
46
+
47
+
48
+ ### Return the index of the specified +char+ in +alphabet+, raising an
49
+ ### appropriate error if it is not found.
50
+ def self::get_char_index( alphabet, char )
51
+ rval = alphabet.index( char ) or
52
+ raise IndexError, "Could not find %p in alphabet %p." % [ char, alphabet ]
53
+ return rval
54
+ end
55
+
56
+
57
+ ### Return the minimum number of characters needed to encode +bytes_size+ bytes
58
+ ### using the given +alphabet+.
59
+ def self::character_block_size( alphabet_size, bytes_size )
60
+ return ( 8 * bytes_size / Math.log2(alphabet_size) ).ceil
61
+ end
62
+
63
+
64
+ ### Return the maximum number of bytes needed to encode +chars_size+ characters
65
+ ### using the given +alphabet+.
66
+ def self::max_bytes_size( alphabet_size, chars_size )
67
+ return ( Math.log2(alphabet_size) / 8 * chars_size ).floor
68
+ end
69
+
70
+
71
+ ### Return the number of bits left over after using an alphabet of the specified
72
+ ### +alphabet_size+ to encode a payload of +bytes_size+ with +chars_size+
73
+ ### characters.
74
+ def self::extra_bits( alphabet_size, chars_size, bytes_size )
75
+ total_bits = ( Math.log2(alphabet_size) * chars_size ).floor
76
+ return total_bits - 8 * bytes_size
77
+ end
78
+
79
+
80
+ ###############
81
+ module_function
82
+ ###############
83
+
84
+ ### Return the +input_bytes+ ascii-armored using the specified +options+
85
+ def armor( input, **options )
86
+ options = Saltpack::Armor::DEFAULT_OPTIONS.merge( options )
87
+ slicer = Saltpack::Armor.slice_enum_for( input, options[:block_size] )
88
+
89
+ output = slicer.
90
+ each_with_object( String.new(encoding: 'us-ascii') ) do |chunk, buf|
91
+ buf << Saltpack::Armor.encode_block( chunk, options[:alphabet], options[:shift] )
92
+ end
93
+
94
+ self.log.debug "Armor output: %p" % [ output ]
95
+
96
+ if options[:raw]
97
+ out_slicer = Saltpack::Armor.slice_enum_for( output, 43 )
98
+ return out_slicer.to_a.join( ' ' )
99
+ end
100
+
101
+ word_slicer = Saltpack::Armor.slice_enum_for( output, 15 )
102
+ sentences = word_slicer.each_slice( 200 )
103
+
104
+ joined = sentences.map {|words| words.to_a.join(' ') }.to_a.join( "\n" )
105
+ header = "BEGIN SALTPACK %s. " % [ options[:message_type] ]
106
+ footer = ". END SALTPACK %s." % [ options[:message_type] ]
107
+
108
+ return header + joined + footer
109
+ end
110
+
111
+
112
+ ### Decode the ascii-armored data from the specified +input_chars+ using
113
+ ### the given +options+.
114
+ def dearmor( input, **options )
115
+ options = Saltpack::Armor::DEFAULT_OPTIONS.merge( options )
116
+
117
+ unless options[:raw]
118
+ _header, input, _footer = input.split( '.', 3 )
119
+ self.log.debug "Stripped input: %p" % [ input ]
120
+ end
121
+
122
+ input = input.gsub( /\p{Space}+/, '' )
123
+ chunks = Saltpack::Armor.slice_enum_for( input, options[:char_block_size] )
124
+
125
+ output = String.new( encoding: 'binary' )
126
+ chunks.each do |chunk|
127
+ output << Saltpack::Armor.decode_block( chunk, options[:alphabet], options[:shift] )
128
+ end
129
+
130
+ return output
131
+ end
132
+
133
+
134
+ ### Encode a single block of ascii-armored output from +bytes_block+ using the
135
+ ### specified +alphabet+ and +shift+.
136
+ def encode_block( bytes_block, alphabet=Saltpack::Armor::B62ALPHABET, shift=false )
137
+ block_size = Saltpack::Armor.character_block_size( alphabet.length, bytes_block.length )
138
+ extra = Saltpack::Armor.extra_bits( alphabet.length, block_size, bytes_block.length )
139
+
140
+ # Convert the bytes into an integer, big-endian.
141
+ bytes_int = bytes_block.unpack1( 'H*' ).hex
142
+
143
+ # Shift left by the extra bits.
144
+ bytes_int <<= extra if shift
145
+
146
+ # Convert the result into our base.
147
+ places = []
148
+ ( 0 ... block_size ).each do |place|
149
+ rem = bytes_int % alphabet.length
150
+ places.unshift( rem )
151
+ bytes_int /= alphabet.length
152
+ end
153
+
154
+ return places.map {|i| alphabet[i] }.join
155
+ end
156
+
157
+
158
+ ### Decode the specified ascii-armored +chars_block+ using the specified
159
+ ### +alphabet+ and +shift+.
160
+ def decode_block( chars_block, alphabet=Saltpack::Armor::B62ALPHABET, shift=false )
161
+ bytes_size = Saltpack::Armor.max_bytes_size( alphabet.length, chars_block.length )
162
+ expected_block_size = Saltpack::Armor.character_block_size( alphabet.length, bytes_size )
163
+
164
+ self.log.debug "For %p with an alphabet of %d chars: bytes_size=%d; expected_block_size=%d" %
165
+ [ chars_block, alphabet.length, bytes_size, expected_block_size ]
166
+
167
+ raise ArgumentError, "illegal block size %d, expected %d" %
168
+ [ chars_block.length, expected_block_size ] unless
169
+ chars_block.length == expected_block_size
170
+
171
+ extra = Saltpack::Armor.extra_bits( alphabet.length, chars_block.length, bytes_size )
172
+
173
+ # Convert the chars to an integer.
174
+ bytes_int = Saltpack::Armor.get_char_index( alphabet, chars_block[0] )
175
+ chars_block[ 1.. ].chars.each do |char|
176
+ bytes_int *= alphabet.length
177
+ bytes_int += Saltpack::Armor.get_char_index( alphabet, char )
178
+ end
179
+
180
+ # Shift right by the extra bits.
181
+ bytes_int >>= extra if shift
182
+
183
+ return [ bytes_int.to_s(16) ].pack( 'H*' )
184
+ end
185
+
186
+
187
+ ### Return a table of the most efficient number of characters to use between 1
188
+ ### and +chars_size_upper_bound+ using an alphabet of +alphabet_size+. Each row
189
+ ### of the resulting Array will be a tuple of:
190
+ ###
191
+ ### [ character_size, byte_size, efficiency ]
192
+ def efficient_chars_sizes( alphabet_size, chars_size_upper_bound=50 )
193
+ out = []
194
+ max_efficiency = 0.0
195
+
196
+ ( 1..chars_size_upper_bound ).each do |chars_size|
197
+ bytes_size = Saltpack::Armor.max_bytes_size( alphabet_size, chars_size )
198
+ efficiency = bytes_size / chars_size.to_f
199
+ self.log.debug "Efficiency for %d/%d: %0.3f" % [ bytes_size, chars_size, efficiency ]
200
+
201
+ if efficiency > max_efficiency
202
+ out << [ chars_size, bytes_size, efficiency ]
203
+ max_efficiency = efficiency
204
+ end
205
+ end
206
+
207
+ return out
208
+ end
209
+
210
+ end # module Saltpack::Armor
211
+