Module: OpenBankingIO::Envelope
- Defined in:
- lib/open_banking_io/envelope.rb
Overview
Decrypts open-banking.io’s zero-knowledge data envelopes.
Scheme: ephemeral ECDH on NIST P-256 -> HKDF-SHA256 -> AES-256-GCM. Wire: version(1)=0x01 | ephemeralPublicKeyRaw(65) | nonce(12) | tag(16) | ciphertext. Only the user’s private key can decrypt – the service stores ciphertext it cannot read.
Constant Summary collapse
- VERSION_BYTE =
0x01- POINT_LEN =
65- NONCE_LEN =
12- TAG_LEN =
16- HKDF_SALT =
("\x00".b * 32).freeze
- HKDF_INFO =
"bank.core.ci/zk/v1".b.freeze
- GROUP =
OpenSSL::PKey::EC::Group.new("prime256v1")
Class Method Summary collapse
-
.decrypt(private_key, envelope_bytes) ⇒ Object
Decrypts the raw bytes of a zero-knowledge envelope, returning the plaintext bytes.
-
.decrypt_to_json(private_key, envelope_b64) ⇒ Object
Decrypts a base64 envelope and parses its JSON payload.
-
.load_private_key(private_key_pkcs8_b64) ⇒ Object
Loads a base64 PKCS#8 EC (P-256) private key.
Class Method Details
.decrypt(private_key, envelope_bytes) ⇒ Object
Decrypts the raw bytes of a zero-knowledge envelope, returning the plaintext bytes.
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 |
# File 'lib/open_banking_io/envelope.rb', line 35 def decrypt(private_key, envelope_bytes) min_len = 1 + POINT_LEN + NONCE_LEN + TAG_LEN if envelope_bytes.bytesize < min_len || envelope_bytes.getbyte(0) != VERSION_BYTE raise ArgumentError, "Invalid or unsupported envelope" end eph_pub_bytes = envelope_bytes.byteslice(1, POINT_LEN) nonce = envelope_bytes.byteslice(1 + POINT_LEN, NONCE_LEN) tag = envelope_bytes.byteslice(1 + POINT_LEN + NONCE_LEN, TAG_LEN) ciphertext = envelope_bytes.byteslice((1 + POINT_LEN + NONCE_LEN + TAG_LEN)..) || "".b pub = OpenSSL::PKey::EC::Point.new(GROUP, OpenSSL::BN.new(eph_pub_bytes, 2)) shared = private_key.dh_compute_key(pub) key = OpenSSL::KDF.hkdf( shared, salt: HKDF_SALT, info: HKDF_INFO, length: 32, hash: "SHA256" ) cipher = OpenSSL::Cipher.new("aes-256-gcm") cipher.decrypt cipher.key = key cipher.iv = nonce cipher.auth_tag = tag cipher.auth_data = "" cipher.update(ciphertext) + cipher.final end |
.decrypt_to_json(private_key, envelope_b64) ⇒ Object
Decrypts a base64 envelope and parses its JSON payload. nil in -> nil out.
67 68 69 70 71 72 |
# File 'lib/open_banking_io/envelope.rb', line 67 def decrypt_to_json(private_key, envelope_b64) return nil if envelope_b64.nil? plaintext = decrypt(private_key, Base64.decode64(envelope_b64)) JSON.parse(plaintext) end |
.load_private_key(private_key_pkcs8_b64) ⇒ Object
Loads a base64 PKCS#8 EC (P-256) private key.
25 26 27 28 29 30 31 32 |
# File 'lib/open_banking_io/envelope.rb', line 25 def load_private_key(private_key_pkcs8_b64) key = OpenSSL::PKey.read(Base64.decode64(private_key_pkcs8_b64)) unless key.is_a?(OpenSSL::PKey::EC) raise ArgumentError, "Private key is not an EC key" end key end |