Class: Saro::Dat::DatCrypto
- Inherits:
-
Object
- Object
- Saro::Dat::DatCrypto
- Defined in:
- lib/saro/dat/crypto.rb
Instance Attribute Summary collapse
-
#algorithm ⇒ Object
readonly
Returns the value of attribute algorithm.
Class Method Summary collapse
Instance Method Summary collapse
-
#decrypt(data) ⇒ Object
Decrypts raw (already decoded) ciphertext: nonce(12) + ciphertext + tag(16).
-
#decrypt_base64(base64_str) ⇒ Object
Decrypts base64url-encoded ciphertext.
- #encrypt(data) ⇒ Object
- #exports ⇒ Object
-
#initialize(algorithm, key_bytes, config = nil) ⇒ DatCrypto
constructor
A new instance of DatCrypto.
Constructor Details
#initialize(algorithm, key_bytes, config = nil) ⇒ DatCrypto
Returns a new instance of DatCrypto.
33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/saro/dat/crypto.rb', line 33 def initialize(algorithm, key_bytes, config = nil) @config = config || Saro::Dat.get_crypto_config(algorithm) # OpenSSL accepts any valid AES length, so a 16-byte key would silently # become AES-128 under an IV-AES256-GCM label without this check. if key_bytes.bytesize != @config[:length] raise Saro::Dat::Error.new( Saro::Dat::ErrorCode::KEY_INVALID, "#{algorithm} key must be #{@config[:length]} bytes, got #{key_bytes.bytesize}" ) end @algorithm = algorithm @key_bytes = key_bytes end |
Instance Attribute Details
#algorithm ⇒ Object (readonly)
Returns the value of attribute algorithm.
31 32 33 |
# File 'lib/saro/dat/crypto.rb', line 31 def algorithm @algorithm end |
Class Method Details
.generate(algorithm) ⇒ Object
47 48 49 50 51 |
# File 'lib/saro/dat/crypto.rb', line 47 def self.generate(algorithm) config = Saro::Dat.get_crypto_config(algorithm) key_bytes = OpenSSL::Random.random_bytes(config[:length]) new(algorithm, key_bytes, config) end |
.imports(algorithm, base64_str) ⇒ Object
53 54 55 56 |
# File 'lib/saro/dat/crypto.rb', line 53 def self.imports(algorithm, base64_str) key_bytes = Saro::Dat::Util.decode_base64_url(base64_str) new(algorithm, key_bytes) end |
Instance Method Details
#decrypt(data) ⇒ Object
Decrypts raw (already decoded) ciphertext: nonce(12) + ciphertext + tag(16). Use #decrypt_base64 for encoded input. The branch used to be picked from the string's encoding tag, so raw ciphertext that merely carried a UTF-8 tag was silently mangled by the base64 decoder.
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 127 128 |
# File 'lib/saro/dat/crypto.rb', line 94 def decrypt(data) return "".b if data.nil? || data.empty? # Slice by bytes, never by characters: String#[] is character-indexed on # a non-binary string, so raw ciphertext carrying a UTF-8 tag would be # split at the wrong offsets. data = data.b if data.is_a?(String) && data.encoding != Encoding::BINARY if data.bytesize <= 12 + 16 # nonce(12) + tag(16) raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CRYPTO_DATA_INVALID, "ciphertext is shorter than iv(12) + tag(16)") end nonce = data[0, 12] tag = data[-16, 16] ciphertext = data[12...-16] cipher = OpenSSL::Cipher.new(@config[:name]) cipher.decrypt cipher.key = @key_bytes cipher.iv_len = 12 cipher.iv = nonce cipher.auth_tag = tag # cipher.final 에서 나는 실패는 GCM 인증 태그 불일치다 — 변조된 secure 이거나 # 잘못된 인증서 키다. 예전에는 OpenSSL::Cipher::CipherError 가 그대로 공개 # API 밖으로 새어 나갔다. 서명 검증을 건너뛰는 경로에서는 이것이 유일한 # 무결성 검사이므로 백엔드 오류(CRYPTO_BACKEND)와 반드시 구분한다. begin res = cipher.update(ciphertext) + cipher.final rescue OpenSSL::Cipher::CipherError => e raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CRYPTO_TAG_MISMATCH, "gcm authentication tag mismatch", cause: e) end res.force_encoding('BINARY') res end |
#decrypt_base64(base64_str) ⇒ Object
Decrypts base64url-encoded ciphertext.
86 87 88 |
# File 'lib/saro/dat/crypto.rb', line 86 def decrypt_base64(base64_str) decrypt(Saro::Dat::Util.decode_base64_url(base64_str)) end |
#encrypt(data) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/saro/dat/crypto.rb', line 62 def encrypt(data) if data.is_a?(String) && data.encoding != Encoding::BINARY && data.encoding != Encoding::UTF_8 data = data.encode('utf-8') end return "".b if data.nil? || data.empty? cipher = OpenSSL::Cipher.new(@config[:name]) cipher.encrypt cipher.key = @key_bytes nonce = OpenSSL::Random.random_bytes(12) cipher.iv_len = 12 cipher.iv = nonce begin ciphertext = cipher.update(data) + cipher.final tag = cipher.auth_tag rescue OpenSSL::Cipher::CipherError => e raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CRYPTO_BACKEND, "aes-gcm encrypt failed", cause: e) end nonce + ciphertext + tag end |
#exports ⇒ Object
58 59 60 |
# File 'lib/saro/dat/crypto.rb', line 58 def exports Saro::Dat::Util.encode_base64_url_str(@key_bytes) end |