Class: CoinOp::Crypto::PassphraseBox
- Inherits:
-
Object
- Object
- CoinOp::Crypto::PassphraseBox
- Extended by:
- Encodings
- Includes:
- Encodings
- Defined in:
- lib/coin-op/crypto.rb
Overview
A wrapper for NaCl’s Secret Box, taking a user-supplied passphrase and deriving a secret key, rather than using a (far more secure) randomly generated secret key.
NaCl Secret Box provides a high level interface for authenticated symmetric encryption. When creating the box, you must supply a key. When using the box to encrypt, you must supply a random nonce. Nonces must never be re-used.
Secret Box decryption requires the ciphertext and the nonce used to create it.
The PassphraseBox class takes a passphrase, rather than a randomly generated key. It uses PBKDF2 to generate a key that, while not random, is somewhat resistant to brute force attacks. Great care should still be taken to avoid passphrases that are subject to dictionary attacks.
Constant Summary collapse
- ITERATIONS =
PBKDF2 work factor
90_000
- ITERATIONS_WINDOW =
20_000
- SALT_RANDOM_BYTES =
16
- KEY_SIZE =
32
- AES_CIPHER =
'AES-256-CBC'
Instance Attribute Summary collapse
-
#salt ⇒ Object
readonly
Returns the value of attribute salt.
Class Method Summary collapse
-
.decrypt(passphrase, hash) ⇒ Object
PassphraseBox.decrypt “my great password”, :salt => salt, :nonce => nonce, :ciphertext => ciphertext.
-
.encrypt(passphrase, plaintext) ⇒ Object
Given passphrase and plaintext as strings, returns a Hash containing the ciphertext and other values needed for later decryption.
Instance Method Summary collapse
- #decrypt(iv, nonce, ciphertext) ⇒ Object
- #decrypt_aes(iv, ciphertext) ⇒ Object
- #decrypt_nacl(nonce, ciphertext) ⇒ Object
- #encrypt(plaintext, iv = @cipher.random_iv) ⇒ Object
-
#initialize(passphrase, mode = :aes, salt = SecureRandom.random_bytes(SALT_RANDOM_BYTES), iterations = nil) ⇒ PassphraseBox
constructor
Initialize with an existing salt and iterations to allow decryption.
Methods included from Encodings
base58, decode_base58, decode_hex, hex, int_to_byte_array
Constructor Details
#initialize(passphrase, mode = :aes, salt = SecureRandom.random_bytes(SALT_RANDOM_BYTES), iterations = nil) ⇒ PassphraseBox
Initialize with an existing salt and iterations to allow decryption. Otherwise, creates new values for these, meaning it creates an entirely new secret-box.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/coin-op/crypto.rb', line 71 def initialize(passphrase, mode=:aes, salt=SecureRandom.random_bytes(SALT_RANDOM_BYTES), iterations=nil) @salt = salt @iterations = iterations || ITERATIONS + SecureRandom.random_number(ITERATIONS_WINDOW) @mode = mode if @mode == :aes @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1( passphrase, @salt, # TODO: decide on a very safe work factor # https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet # @iterations, # number of iterations KEY_SIZE * 2 # key length in bytes ) @aes_key = @key[0, KEY_SIZE] @hmac_key = @key[KEY_SIZE, KEY_SIZE] @cipher = OpenSSL::Cipher.new(AES_CIPHER) @cipher.padding = 0 elsif @mode == :nacl @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1( passphrase, @salt, # TODO: decide on a very safe work factor # https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet # @iterations, # number of iterations KEY_SIZE # key length in bytes ) @box = RbNaCl::SecretBox.new(@key) end end |
Instance Attribute Details
#salt ⇒ Object (readonly)
Returns the value of attribute salt.
66 67 68 |
# File 'lib/coin-op/crypto.rb', line 66 def salt @salt end |
Class Method Details
.decrypt(passphrase, hash) ⇒ Object
PassphraseBox.decrypt “my great password”,
:salt => salt, :nonce => nonce, :ciphertext => ciphertext
51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/coin-op/crypto.rb', line 51 def self.decrypt(passphrase, hash) salt, iv, nonce, ciphertext = hash.values_at(:salt, :iv, :nonce, :ciphertext).map {|s| decode_hex(s) } mode = nil if iv.empty? mode = :nacl elsif nonce.empty? mode = :aes end box = self.new(passphrase, mode, salt, hash[:iterations] || ITERATIONS) box.decrypt(iv, nonce, ciphertext) end |
.encrypt(passphrase, plaintext) ⇒ Object
Given passphrase and plaintext as strings, returns a Hash containing the ciphertext and other values needed for later decryption. Binary values are encoded as hexadecimal strings.
43 44 45 46 |
# File 'lib/coin-op/crypto.rb', line 43 def self.encrypt(passphrase, plaintext) box = self.new(passphrase) box.encrypt(plaintext) end |
Instance Method Details
#decrypt(iv, nonce, ciphertext) ⇒ Object
123 124 125 126 127 128 129 130 |
# File 'lib/coin-op/crypto.rb', line 123 def decrypt(iv, nonce, ciphertext) if @mode == :aes return decrypt_aes(iv, ciphertext) elsif @mode == :nacl return decrypt_nacl(nonce, ciphertext) end raise('Incompatible ciphertext') end |
#decrypt_aes(iv, ciphertext) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/coin-op/crypto.rb', line 132 def decrypt_aes(iv, ciphertext) mac, ctext = ciphertext[-KEY_SIZE, KEY_SIZE], ciphertext[0...-KEY_SIZE] digest = OpenSSL::Digest::SHA256.new hmac_digest = OpenSSL::HMAC.digest(digest, @hmac_key, iv + ctext) if hmac_digest != mac raise('Invalid authentication code - this ciphertext may have been tampered with.') end @cipher.decrypt @cipher.iv = iv @cipher.key = @aes_key decrypted = @cipher.update(ctext) decrypted << @cipher.final end |
#decrypt_nacl(nonce, ciphertext) ⇒ Object
146 147 148 |
# File 'lib/coin-op/crypto.rb', line 146 def decrypt_nacl(nonce, ciphertext) @box.decrypt(nonce, ciphertext) end |
#encrypt(plaintext, iv = @cipher.random_iv) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/coin-op/crypto.rb', line 106 def encrypt(plaintext, iv=@cipher.random_iv) @cipher.encrypt @cipher.iv = iv @cipher.key = @aes_key encrypted = @cipher.update(plaintext) encrypted << @cipher.final digest = OpenSSL::Digest::SHA256.new hmac_digest = OpenSSL::HMAC.digest(digest, @hmac_key, iv + encrypted) ciphertext = encrypted + hmac_digest { iterations: @iterations, salt: hex(@salt), iv: hex(iv), ciphertext: hex(ciphertext) } end |