botan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+