Class: Keystores::Jks::KeyProtector
- Inherits:
-
Object
- Object
- Keystores::Jks::KeyProtector
- Defined in:
- lib/keystores/jks/key_protector.rb
Constant Summary collapse
- SALT_LEN =
20- DIGEST_LEN =
20- KEY_PROTECTOR_OID =
'1.3.6.1.4.1.42.2.17.1.1'
Instance Method Summary collapse
-
#initialize(password) ⇒ KeyProtector
constructor
A new instance of KeyProtector.
- #protect(key) ⇒ Object
- #recover(encrypted_private_key_info) ⇒ Object
Constructor Details
#initialize(password) ⇒ KeyProtector
Returns a new instance of KeyProtector.
60 61 62 63 64 65 66 67 68 |
# File 'lib/keystores/jks/key_protector.rb', line 60 def initialize(password) @password = password @passwd_bytes = [] password.unpack('c*').each do |byte| @passwd_bytes << (byte >> 8) @passwd_bytes << byte end = OpenSSL::Digest::SHA1.new end |
Instance Method Details
#protect(key) ⇒ Object
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 |
# File 'lib/keystores/jks/key_protector.rb', line 70 def protect(key) if key.nil? raise ArgumentError.new("plaintext key can't be null") end plain_key = key.to_pkcs8_der.unpack('c*') # Determine the number of digest rounds num_rounds = plain_key.length / DIGEST_LEN num_rounds += 1 if (plain_key.length % DIGEST_LEN) != 0 salt = SecureRandom.random_bytes(SALT_LEN) xor_key = Array.new(plain_key.length, 0) xor_offset = 0 digest = salt # Compute the digests, and store them in xor_key for i in 1..num_rounds .update(@passwd_bytes.pack('c*')) .update(digest) digest = .digest .reset if i < num_rounds xor_key[xor_offset..(digest.length + xor_offset -1)] = digest.bytes else xor_key[xor_offset..-1] = digest[0..(xor_key.length - xor_offset - 1)].bytes end xor_offset += DIGEST_LEN end # XOR plain_key with xor_key, and store the result in tmpKey tmp_key = [] for i in 0..(plain_key.length - 1) tmp_key[i] = plain_key[i] ^ xor_key[i] end # Store salt and tmp_key in encr_key encr_key = salt.unpack('c*') + tmp_key # Append digest(password, plain_key) as an integrity check to encr_key << @passwd_bytes.pack('c*') @passwd_bytes.fill(0) @passwd_bytes = nil << plain_key.pack('c*') digest = .digest .reset encr_key += digest.unpack('c*') Keystores::Jks::EncryptedPrivateKeyInfo.new(:algorithm => KEY_PROTECTOR_OID, :encrypted_data => encr_key.pack('c*')).encoded end |
#recover(encrypted_private_key_info) ⇒ Object
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/keystores/jks/key_protector.rb', line 124 def recover(encrypted_private_key_info) unless encrypted_private_key_info.algorithm == KEY_PROTECTOR_OID raise IOError.new("Unsupported key protection algorithm: #{encrypted_private_key_info.algorithm}") end protected_key = encrypted_private_key_info.encrypted_data # Get the salt associated with this key (the first SALT_LEN bytes of protected_key) salt = protected_key.slice(0..(SALT_LEN - 1)) # Determine the number of digest rounds encr_key_len = protected_key.length - SALT_LEN - DIGEST_LEN num_rounds = encr_key_len / DIGEST_LEN num_rounds += 1 if (encr_key_len % DIGEST_LEN) != 0 # Get the encrypted key portion encr_key = protected_key.slice(SALT_LEN..(encr_key_len + SALT_LEN - 1)) xor_key = Array.new(encr_key.size, 0) xor_offset = 0 digest = salt # Compute the digests, and store them in xor_key for i in 1..num_rounds .update(@passwd_bytes.pack('c*')) .update(digest) digest = .digest .reset if i < num_rounds xor_key[xor_offset..(digest.length + xor_offset -1)] = digest.bytes else xor_key[xor_offset..-1] = digest[0..(xor_key.length - xor_offset - 1)].bytes end xor_offset += DIGEST_LEN end # XOR encr_key with xor_key, and store the result in plain_key plain_key = [] encr_key_unpacked = encr_key.bytes for i in 0..(encr_key.size - 1) plain_key[i] = encr_key_unpacked[i] ^ xor_key[i] end # Check the integrity of the recovered key by concatenating it with # the password, digesting the concatenation, and comparing the # result of the digest operation with the digest provided at the end # of protected_key. If the two digest values are # * different, raise an error. << @passwd_bytes.pack('c*') @passwd_bytes.fill(0) @passwd_bytes = nil << plain_key.pack('c*') digest = .digest .reset for i in 0..(digest.length - 1) if digest[i] != protected_key[SALT_LEN + encr_key_len + i] raise IOError.new('Cannot recover key') end end OpenSSL::PKey.pkcs8_parse(plain_key.pack('c*')) end |