Module: SessionKeys
- Defined in:
- lib/session_keys.rb,
lib/session_keys/version.rb
Overview
SessionKeys deterministic cryptographic key generation.
Constant Summary collapse
- SCRYPT_DIGEST_SIZE =
Size in bytes of the scrypt derived output
256- VERSION =
'2.0.0'
Class Method Summary collapse
-
.generate(id, password, min_password_entropy = 75) ⇒ Hash
Deterministically generates a collection of derived encryption key material from a provided id and password/passphrase.
Class Method Details
.generate(id, password, min_password_entropy = 75) ⇒ Hash
Deterministically generates a collection of derived encryption key material from a provided id and password/passphrase. Uses SHA256 and scrypt for key derivation.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/session_keys.rb', line 24 def self.generate(id, password, min_password_entropy = 75) unless id.is_a?(String) && ['US-ASCII', 'UTF-8'].include?(id.encoding.name) raise ArgumentError, 'invalid id, not a US-ASCII or UTF-8 string' end unless id.length.between?(1,256) raise ArgumentError, 'invalid id, must be between 1 and 256 characters in length' end unless password.is_a?(String) && ['US-ASCII', 'UTF-8'].include?(password.encoding.name) raise ArgumentError, 'invalid password, not a US-ASCII or UTF-8 string' end # Enforce max length only due to Zxcvbn taking a *long* time to # process long strings and determine entropy. unless password.length.between?(1,256) raise ArgumentError, 'invalid password, must be between 1 and 256 characters in length' end unless min_password_entropy.is_a?(Integer) && min_password_entropy.between?(1, 512) raise ArgumentError, 'invalid min_password_entropy, must be an Integer between 1 and 512' end password_test = Zxcvbn.test(password) unless password_test.entropy.round >= min_password_entropy raise ArgumentError, "invalid password, must be at least #{min_password_entropy} bits of estimated entropy" end start_processing_time = Time.now id_sha256_bytes = RbNaCl::Hash.sha256(id.bytes.pack('C*')) id_sha256_hex = id_sha256_bytes.bytes.map { |byte| '%02x' % byte }.join # libsodium : By design, a password whose length is 65 bytes or more is # reduced to SHA-256(password). This can have security implications if the # password is present in another password database using raw, unsalted # SHA-256. Or when upgrading passwords previously hashed with unsalted # SHA-256 to scrypt. If this is a concern, passwords should be pre-hashed # before being hashed using scrypt. scrypt_key = RbNaCl::Hash.sha256(password.bytes.pack('C*')) # Tie the sycrypt password bytes to the ID they are associate with by # utilizing the ID as the salt. Include the ID length and an additional # string to harden the salt. scrypt_salt = RbNaCl::Hash.sha256([id_sha256_hex, id_sha256_hex.length, 'session_keys'].join('').bytes.pack('C*')) # Derive SCRYPT_DIGEST_SIZE secret bytes password_digest = RbNaCl::PasswordHash.scrypt( scrypt_key, scrypt_salt, 16384 * 32, 16384 * 32 * 32, SCRYPT_DIGEST_SIZE ).bytes num_keys = SCRYPT_DIGEST_SIZE / 32 byte_keys = [] num_keys.times { byte_keys << password_digest.shift(32) } hex_keys = byte_keys.map { |key| key.map { |byte| '%02x' % byte }.join } nacl_encryption_key_pairs = byte_keys.map { |key| seed = key.pack('C*').force_encoding('ASCII-8BIT') sec_key = RbNaCl::PrivateKey.new(seed) pub_key = sec_key.public_key {secret_key: sec_key, public_key: pub_key} } nacl_encryption_key_pairs_base64 = nacl_encryption_key_pairs.map { |keypair| pub_key = Base64.strict_encode64(keypair[:public_key].to_bytes) sec_key = Base64.strict_encode64(keypair[:secret_key].to_bytes) {secret_key: sec_key, public_key: pub_key} } nacl_signing_key_pairs = byte_keys.map { |key| seed = key.pack('C*').force_encoding('ASCII-8BIT') sec_key = RbNaCl::SigningKey.new(seed) pub_key = sec_key.verify_key {secret_key: sec_key, public_key: pub_key} } nacl_signing_key_pairs_base64 = nacl_signing_key_pairs.map { |keypair| pub_key = Base64.strict_encode64(keypair[:public_key].to_bytes) sec_key = Base64.strict_encode64(keypair[:secret_key].to_bytes) {secret_key: sec_key, public_key: pub_key} } { id: id_sha256_hex, byte_keys: byte_keys, hex_keys: hex_keys, nacl_encryption_key_pairs: nacl_encryption_key_pairs, nacl_encryption_key_pairs_base64: nacl_encryption_key_pairs_base64, nacl_signing_key_pairs: nacl_signing_key_pairs, nacl_signing_key_pairs_base64: nacl_signing_key_pairs_base64, process_time: ((Time.now - start_processing_time)*1000).round(2) } end |