saltpack 0.1.0.pre.20200506181314
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rdoc_options +16 -0
- data/.simplecov +9 -0
- data/ChangeLog +55 -0
- data/History.md +4 -0
- data/LICENSE.txt +19 -0
- data/Manifest.txt +25 -0
- data/README.md +89 -0
- data/Rakefile +9 -0
- data/examples/post_signed_message.rb +21 -0
- data/lib/saltpack.rb +112 -0
- data/lib/saltpack/armor.rb +211 -0
- data/lib/saltpack/errors.rb +23 -0
- data/lib/saltpack/header.rb +299 -0
- data/lib/saltpack/message.rb +101 -0
- data/lib/saltpack/payload.rb +24 -0
- data/lib/saltpack/recipient.rb +33 -0
- data/lib/saltpack/refinements.rb +38 -0
- data/spec/data/msg1-ciphertext.txt +1 -0
- data/spec/data/msg1.txt +16 -0
- data/spec/saltpack/armor_spec.rb +57 -0
- data/spec/saltpack/header_spec.rb +87 -0
- data/spec/saltpack/recipient_spec.rb +25 -0
- data/spec/saltpack_spec.rb +86 -0
- data/spec/spec_helper.rb +43 -0
- metadata +193 -0
checksums.yaml
ADDED
@@ -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
|
data/.document
ADDED
data/.rdoc_options
ADDED
@@ -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:
|
data/.simplecov
ADDED
data/ChangeLog
ADDED
@@ -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]
|
data/History.md
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/Manifest.txt
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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 <oconnor663+pypi@gmail.com>, 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
|
data/Rakefile
ADDED
@@ -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
|
+
|
data/lib/saltpack.rb
ADDED
@@ -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
|
+
|