saltpack 0.1.0.pre.20200506181314

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.
@@ -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
+