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