botan 0.1.0

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
+ SHA1:
3
+ metadata.gz: c3e3d7ff23f00256e3b507bdbc67029f88887aa7
4
+ data.tar.gz: 2eff70990334914d19595cb04907f50256664674
5
+ SHA512:
6
+ metadata.gz: fd92e2df19b1b3b5092057cd3497774c263c9371caf12dd36df2e8355a20ae1446de63b090570c24d02581d3d9387e2ddedbfc72f85cb18aa905c3ffb01e47eb
7
+ data.tar.gz: 9280656ff057c834d6fffdd40448aadbcf6492b88121ac364988f44eeb98189035e938fa98c33feab2099d04a4bc6a0bc011897fcaa1f25a30d01cb5d1f8a570
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Ribose Inc.
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,219 @@
1
+ # ruby-botan [![codecov.io](https://codecov.io/github/riboseinc/ruby-botan/coverage.svg?branch=master)](https://codecov.io/github/riboseinc/ruby-botan?branch=master)
2
+
3
+ ruby-botan is a Ruby interface to [Botan](https://botan.randombit.net/).
4
+
5
+ **Note**: Refer to the Botan documentation in addition to the documentation here. In particular, this note from the Botan manual applies here as well:
6
+
7
+ > You should have some knowledge of cryptography **before** trying to use the library. This is an area where it is very easy to make mistakes, and where things are often subtle and/or counterintuitive. Obviously the library tries to provide things at a high level precisely to minimize the number of ways things can go wrong, but naive use will almost certainly not result in a secure system.
8
+
9
+ # Requirements
10
+
11
+ ## Ruby
12
+
13
+ ruby-botan is currently tested to work with:
14
+
15
+ * Ruby 2.3
16
+ * Ruby 2.4
17
+
18
+ ## Botan
19
+
20
+ [Botan](https://botan.randombit.net/) version 2.2 or newer is required.
21
+
22
+ # Basic Usage
23
+
24
+ The samples below are meant to be a brief introduction to the library. Refer to the full documentation for full details.
25
+
26
+ Also see the [examples](https://github.com/riboseinc/ruby-botan/tree/master/examples) directory for examples on using various parts of the library.
27
+
28
+ ## Utilities
29
+
30
+ ```ruby
31
+ Botan.hex_encode("\x01\x02\x03\x04")
32
+
33
+ Botan.hex_decode('01020304')
34
+ ```
35
+
36
+ ## RNG - Random Number Generation
37
+
38
+ ```ruby
39
+ # shortcut method that uses default RNG to get 10 bytes
40
+ Botan::RNG.get(10)
41
+
42
+ # create a different type of RNG, and reseed from the system RNG
43
+ rng = Botan::RNG.new('user')
44
+ rng.reseed
45
+ rng.get(5)
46
+ ```
47
+
48
+ ## Digest / Hash
49
+
50
+ There are a few different ways to utilize Digest. Which method you choose may depend on whether the data is immediately available in full, or whether it is becoming available over time.
51
+
52
+ ### Simple One-Shot Hash
53
+
54
+ If you simply want to hash some data that you have immediately available in full, you may want to do something like the following.
55
+
56
+ ```ruby
57
+ Botan::Digest::MD5.digest('my data')
58
+
59
+ Botan::Digest::SHA256.hexdigest('my data')
60
+ ```
61
+
62
+ You may also use a longer form, in case there is not a pre-defined class (like `MD5` and `SHA256` above). For example:
63
+
64
+ ```ruby
65
+ Botan::Digest.digest('Comb4P(SHA-160,RIPEMD-160)', 'my data')
66
+
67
+ Botan::Digest.hexdigest('SHA-3(224)', 'my data')
68
+ ```
69
+
70
+ ### Continuously Updated Hash
71
+
72
+ If you have a stream of incoming data that is not readily available that you want to hash, you may proceed in a couple of ways:
73
+
74
+ ```ruby
75
+ # MD5
76
+ md5 = Botan::Digest::MD5.new
77
+ md5.update('my ')
78
+ md5 << 'data'
79
+ md5.hexdigest
80
+
81
+ # Comb4P hash combiner
82
+ hash = Botan::Digest.new('Comb4P(SHA-160,RIPEMD-160)')
83
+ hash << 'my '
84
+ hash << 'data'
85
+ hash.hexdigest
86
+ ```
87
+
88
+ ## Cipher
89
+
90
+ ```ruby
91
+ # encrypt
92
+ enc = Botan::Cipher.encryption('AES-128/CBC/PKCS7')
93
+ key = Botan::RNG.get(enc.key_length_max)
94
+ iv = Botan::RNG.get(enc.default_nonce_length)
95
+ enc.key = key
96
+ enc.iv = iv
97
+ ciphertext = enc.finish('my data')
98
+
99
+ # decrypt
100
+ dec = Botan::Cipher.decryption('AES-128/CBC/PKCS7')
101
+ dec.key = key
102
+ dec.iv = iv
103
+ plaintext = dec.finish(ciphertext)
104
+ ```
105
+
106
+ ## BCrypt
107
+
108
+ The `Botan::BCrypt` module supports simple bcrypt password hashing.
109
+
110
+ ```ruby
111
+ # generate password hash
112
+ password_input = gets.chomp
113
+ password_hash = Botan::BCrypt.hash(password_input, work_factor: 10)
114
+
115
+ # check password
116
+ password_input = gets.chomp
117
+ Botan::BCrypt.valid?(password: password_input, phash: password_hash)
118
+ ```
119
+
120
+ ## KDF - Key Derivation Functions
121
+
122
+ The `Botan::KDF` module has a few different functions for key derivation.
123
+
124
+ ```ruby
125
+ Botan::KDF.kdf(algo: 'KDF2(SHA-160)', secret: Botan::RNG.get(9), salt: Botan::RNG.get(7), key_length: 32)
126
+
127
+ Botan::KDF.pbkdf(algo: 'PBKDF2(CMAC(Blowfish))', password: 'some long passphrase', iterations: 150_000, key_length: 16)
128
+
129
+ Botan::KDF.pbkdf_timed(algo: 'PBKDF2(SHA-256)', password: 'my secret passphrase', key_length: 8, milliseconds: 100)
130
+ ```
131
+
132
+ ## MAC - Message Authentication Code
133
+
134
+ ```ruby
135
+ hmac = Botan::MAC.new('HMAC(SHA-256)')
136
+ hmac.key = Botan.hex_decode('0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20')
137
+ hmac << "\x61\x62\x63"
138
+ hmac.hexdigest
139
+ ```
140
+
141
+ ## PK - Public Key Cryptography
142
+
143
+ The `Botan::PK` module exposes functionality for public key loading, exporting, encryption, decryption, signing, and verification.
144
+
145
+ ### Key Generation
146
+
147
+ ```ruby
148
+ # generate a 4096-bit RSA key
149
+ privkey = Botan::PK::PrivateKey.generate('RSA', params: '4096')
150
+
151
+ # generate an ECDSA key with group secp384r1
152
+ privkey = Botan::PK::PrivateKey.generate('ECDSA', params: 'secp384r1')
153
+
154
+ # generate a 4096-bit ElGamal key
155
+ privkey = Botan::PK::PrivateKey.generate('ElGamal', params: 'modp/ietf/4096')
156
+ ```
157
+
158
+ ### Key Loading
159
+
160
+ ```ruby
161
+ # load a public key
162
+ pubkey = Botan::PK::PublicKey.from_data(File.read('some_file.pem'))
163
+
164
+ # load an encrypted private key
165
+ privkey = Botan::PK::PrivateKey.from_data(File.read('some_file.pem'), password: 'my key password')
166
+
167
+ # load an unencrypted private key
168
+ privkey = Botan::PK::PrivateKey.from_data(File.read('some_file.pem'))
169
+ ```
170
+
171
+ ### Key Exporting
172
+
173
+ ```ruby
174
+ # private key export (PEM)
175
+ pem = privkey.export_pem(password: 'my secret password')
176
+
177
+ # public key export (PEM)
178
+ pem = pubkey.export_pem
179
+ ```
180
+
181
+ ### Encryption / Decryption
182
+
183
+ ```ruby
184
+ # using defaults
185
+ ciphertext = pubkey.encrypt('my data')
186
+ plaintext = privkey.decrypt(ciphertext)
187
+ ```
188
+
189
+ ### Signing / Verifying
190
+
191
+ ```ruby
192
+ data = 'my data'
193
+
194
+ # using defaults
195
+ signature = privkey.sign(data)
196
+ valid = pubkey.verify(data: data, signature: signature)
197
+ ```
198
+
199
+ ## X.509 Certificates
200
+
201
+ ### Certificate Loading
202
+
203
+ ```ruby
204
+ # load from a file
205
+ cert = Botan::X509::Certificate.from_file('my cert.crt')
206
+
207
+ # load from some data in memory
208
+ cert = Botan::X509::Certificate.from_data(File.read('my cert.crt'))
209
+ ```
210
+
211
+ ### Certificate Properties
212
+
213
+ ```ruby
214
+ # fingerprint
215
+ fpr = cert.fingerprint('SHA-256')
216
+
217
+ # subject's public key
218
+ pubkey = cert.subject_public_key
219
+ ```
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2017 Ribose Inc.
4
+
5
+ require 'botan/bcrypt'
6
+ require 'botan/cipher'
7
+ require 'botan/defaults'
8
+ require 'botan/error'
9
+ require 'botan/ffi/libbotan'
10
+ require 'botan/digest'
11
+ require 'botan/kdf'
12
+ require 'botan/mac'
13
+ require 'botan/pk/mceies'
14
+ require 'botan/pk/op/decrypt'
15
+ require 'botan/pk/op/encrypt'
16
+ require 'botan/pk/op/keyagreement'
17
+ require 'botan/pk/op/sign'
18
+ require 'botan/pk/op/verify'
19
+ require 'botan/pk/privatekey'
20
+ require 'botan/pk/publickey'
21
+ require 'botan/rng'
22
+ require 'botan/x509/constraints'
23
+ require 'botan/x509/certificate'
24
+ require 'botan/version'
25
+
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2017 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'botan/rng'
8
+ require 'botan/utils'
9
+
10
+ module Botan
11
+ # bcrypt password hashing
12
+ #
13
+ # == Examples
14
+ # === examples/bcrypt.rb
15
+ # {include:file:examples/bcrypt.rb}
16
+ #
17
+ module BCrypt
18
+ # Generates a password hash using bcrypt.
19
+ #
20
+ # @param password [String] the password to hash
21
+ # @param work_factor [Integer] the bcrypt work factor
22
+ # @param rng [Botan::RNG] the RNG to use
23
+ # @return [String] the generated password hash
24
+ def self.hash(password, work_factor: 10, rng: Botan::RNG.new)
25
+ out_len = 64
26
+ out_buf = FFI::MemoryPointer.new(:uint8, out_len)
27
+ flags = 0
28
+ out_len_ptr = FFI::MemoryPointer.new(:size_t)
29
+ out_len_ptr.write(:size_t, out_len)
30
+ Botan.call_ffi(:botan_bcrypt_generate,
31
+ out_buf, out_len_ptr,
32
+ password, rng.ptr, work_factor, flags)
33
+ result = out_buf.read_bytes(out_len_ptr.read(:size_t))
34
+ result = result[0...-1] if result[-1] == "\x00"
35
+ result
36
+ end
37
+
38
+ # Checks a password against a bcrypt hash.
39
+ #
40
+ # @param password [String] the password to hash
41
+ # @param phash [String] the bcrypt hash
42
+ # @return [Boolean] true if the provided password is correct
43
+ def self.valid?(password:, phash:)
44
+ rc = Botan.call_ffi_rc(:botan_bcrypt_is_valid, password, phash)
45
+ rc.zero?
46
+ end
47
+ end # module
48
+ end # module
49
+
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ # (c) 2017 Ribose Inc.
4
+
5
+ require 'ffi'
6
+
7
+ require 'botan/error'
8
+ require 'botan/ffi/libbotan'
9
+ require 'botan/utils'
10
+
11
+ module Botan
12
+ # Cipher
13
+ #
14
+ # == Examples
15
+ # === examples/cipher.rb
16
+ # {include:file:examples/cipher.rb}
17
+ #
18
+ class Cipher
19
+ # Prefer the shortcuts {encryption} and {decryption} instead.
20
+ #
21
+ # @param algo [String] the algorithm to use (example: AES-128/CTR-BE)
22
+ # @param encrypt [Boolean] true if this will be used for encryption,
23
+ # false if it will be used for decryption.
24
+ def initialize(algo, encrypt:)
25
+ flags = encrypt ? 0 : 1
26
+ ptr = FFI::MemoryPointer.new(:pointer)
27
+ Botan.call_ffi(:botan_cipher_init, ptr, algo, flags)
28
+ ptr = ptr.read_pointer
29
+ raise Botan::Error, 'botan_cipher_init returned NULL' if ptr.null?
30
+ @ptr = FFI::AutoPointer.new(ptr, self.class.method(:destroy))
31
+ end
32
+
33
+ # @api private
34
+ def self.destroy(ptr)
35
+ LibBotan.botan_cipher_destroy(ptr)
36
+ end
37
+
38
+ # Creates a new cipher instance for encryption.
39
+ #
40
+ # @param algo [String] the algorithm to use (example: AES-128/CTR-BE)
41
+ # @return [Botan::Cipher] the cipher instance
42
+ def self.encryption(algo)
43
+ Cipher.new(algo, encrypt: true)
44
+ end
45
+
46
+ # Creates a new cipher instance for decryption.
47
+ #
48
+ # @param algo [String] the algorithm to use (example: AES-128/CTR-BE)
49
+ # @return [Botan::Cipher] the cipher instance
50
+ def self.decryption(algo)
51
+ Cipher.new(algo, encrypt: false)
52
+ end
53
+
54
+ # Retrieves the default nonce length for the cipher.
55
+ #
56
+ # @return [Integer]
57
+ def default_nonce_length
58
+ length_ptr = FFI::MemoryPointer.new(:size_t)
59
+ Botan.call_ffi(:botan_cipher_get_default_nonce_length, @ptr, length_ptr)
60
+ length_ptr.read(:size_t)
61
+ end
62
+
63
+ # Retrieves the update granularity for the cipher.
64
+ #
65
+ # @return [Integer]
66
+ def update_granularity
67
+ gran_ptr = FFI::MemoryPointer.new(:size_t)
68
+ Botan.call_ffi(:botan_cipher_get_update_granularity, @ptr, gran_ptr)
69
+ gran_ptr.read(:size_t)
70
+ end
71
+
72
+ # Retrieves the minimum key length for the cipher.
73
+ #
74
+ # @return [Integer]
75
+ def key_length_min
76
+ key_lengths[0]
77
+ end
78
+
79
+ # Retrieves the maximum key length for the cipher.
80
+ #
81
+ # @return [Integer]
82
+ def key_length_max
83
+ key_lengths[1]
84
+ end
85
+
86
+ # Retrieves the tag length when using AEAD modes.
87
+ #
88
+ # @return [Integer]
89
+ def tag_length
90
+ length_ptr = FFI::MemoryPointer.new(:size_t)
91
+ Botan.call_ffi(:botan_cipher_get_tag_length, @ptr, length_ptr)
92
+ length_ptr.read(:size_t)
93
+ end
94
+
95
+ # Determines whether this is an AEAD mode.
96
+ #
97
+ # @return [Boolean] true if this is an AEAD mode
98
+ def authenticated?
99
+ tag_length.positive?
100
+ end
101
+
102
+ # Checks whether a nonce length is valid for this cipher.
103
+ #
104
+ # @param nonce_len [Integer] the nonce length to check
105
+ # @return [Boolean] true if the provided nonce length is valid
106
+ def valid_nonce_length?(nonce_len)
107
+ rc = Botan.call_ffi_rc(:botan_cipher_valid_nonce_length, @ptr, nonce_len)
108
+ rc == 1
109
+ end
110
+
111
+ # Resets the cipher state.
112
+ #
113
+ # @return [self]
114
+ def reset
115
+ Botan.call_ffi(:botan_cipher_clear, @ptr)
116
+ self
117
+ end
118
+
119
+ # Sets the key to be used for the cipher.
120
+ #
121
+ # This should generally be the first thing called after
122
+ # creating a new cipher instance (or after reset).
123
+ #
124
+ # @param key [String] the key
125
+ def key=(key)
126
+ key_buf = FFI::MemoryPointer.from_data(key)
127
+ Botan.call_ffi(:botan_cipher_set_key, @ptr, key_buf, key_buf.size)
128
+ end
129
+
130
+ # Sets the IV to be used for the cipher.
131
+ #
132
+ # This should generally be called after {#key=} or after
133
+ # {#auth_data=} (if using AEAD).
134
+ #
135
+ # @param iv [String] the IV
136
+ def iv=(iv)
137
+ start(iv)
138
+ end
139
+
140
+ # Sets the associated data when using AEAD modes.
141
+ #
142
+ # This should be called *after* {#key=} and before {#iv=}.
143
+ #
144
+ # @param ad [String] the associated data
145
+ def auth_data=(ad)
146
+ ad_buf = FFI::MemoryPointer.from_data(ad)
147
+ Botan.call_ffi(:botan_cipher_set_associated_data, @ptr, ad_buf, ad.size)
148
+ end
149
+
150
+ # Process the data (encrypt/decrypt).
151
+ #
152
+ # @param data [String] the data to encrypt or decrypt.
153
+ # The size should likely be a multiple of {#update_granularity}.
154
+ # @return [String] the ciphertext or plaintext
155
+ def update(data)
156
+ _update(data, final: false)
157
+ end
158
+
159
+ # Finalize the message processing.
160
+ #
161
+ # It is perfectly valid to skip {#update} and pass your
162
+ # entire message here.
163
+ #
164
+ # *Note*: Some ciphers may require a final piece of data of
165
+ # a certain size. See minimum_final_size in the Botan documentation.
166
+ #
167
+ # @param data [String] the data, if any
168
+ # @return [String] the ciphertext or plaintext
169
+ def finish(data = nil)
170
+ _update(data, final: true)
171
+ end
172
+
173
+ def inspect
174
+ Botan.inspect_ptr(self)
175
+ end
176
+
177
+ private
178
+
179
+ def start(nonce)
180
+ nonce_buf = FFI::MemoryPointer.from_data(nonce)
181
+ Botan.call_ffi(:botan_cipher_start, @ptr, nonce_buf, nonce_buf.size)
182
+ end
183
+
184
+ def key_lengths
185
+ kmin_ptr = FFI::MemoryPointer.new(:size_t)
186
+ kmax_ptr = FFI::MemoryPointer.new(:size_t)
187
+ Botan.call_ffi(:botan_cipher_query_keylen, @ptr, kmin_ptr, kmax_ptr)
188
+ [kmin_ptr.read(:size_t), kmax_ptr.read(:size_t)]
189
+ end
190
+
191
+ def _update(data, final:)
192
+ inp = data ? data : ''
193
+ flags = final ? 1 : 0
194
+ out_buf_size = inp.bytesize + (final ? tag_length : 0)
195
+ # FIXME: botan currently lacks a way of determining the size required
196
+ # here, taking in to account padding mechanism, etc.
197
+ out_buf_size += 128
198
+ out_buf = FFI::MemoryPointer.new(:uint8, out_buf_size)
199
+ out_written_ptr = FFI::MemoryPointer.new(:size_t)
200
+ input_buf = FFI::MemoryPointer.from_data(inp)
201
+ inp_consumed_ptr = FFI::MemoryPointer.new(:size_t)
202
+ Botan.call_ffi(:botan_cipher_update, @ptr, flags, out_buf, out_buf.size,
203
+ out_written_ptr, input_buf, input_buf.size,
204
+ inp_consumed_ptr)
205
+ consumed = inp_consumed_ptr.read(:size_t)
206
+ if consumed != inp.bytesize
207
+ raise Botan::Error, 'botan_cipher_update did not consume all input' \
208
+ " (#{consumed} out of #{inp.bytesize} bytes)"
209
+ end
210
+ out_buf.read_bytes(out_written_ptr.read(:size_t))
211
+ end
212
+ end # class
213
+ end # module
214
+