Active Support 訊息加密器
MessageEncryptor
是一種簡易的方式,用於加密儲存在不信任位置的值。
密文和初始化向量會以 base64 編碼並傳回給您。
它可用於類似 MessageVerifier
的情況中,但您不希望使用者能夠判斷有效負載的值。
len = ActiveSupport::MessageEncryptor.key_len
salt = SecureRandom.random_bytes(len)
key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..."
crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
如果所提供的資料無法解密或驗證,decrypt_and_verify
方法會引發 ActiveSupport::MessageEncryptor::InvalidMessage
例外。
crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
將訊息限制於特定目的
預設情況下,任何訊息都可以在應用程式中使用。但它們也可以限制在特定的 :purpose
中。
token = crypt.encrypt_and_sign("this is the chair", purpose: :login)
接著,驗證時必須傳遞相同的目的才能取得資料
crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair"
crypt.decrypt_and_verify(token, purpose: :shipping) # => nil
crypt.decrypt_and_verify(token) # => nil
同樣地,如果訊息沒有目的,它在以特定目的驗證時不會被傳回。
token = crypt.encrypt_and_sign("the conversation is lively")
crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil
crypt.decrypt_and_verify(token) # => "the conversation is lively"
讓訊息過期
預設情況下,訊息會永久保存,從現在起一年後驗證仍然會傳回原始值。但是,訊息可以使用 `:expires_in` 或 `:expires_at` 設定在特定時間過期。
crypt.encrypt_and_sign(parcel, expires_in: 1.month)
crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year)
接著,可以在過期時間之前驗證和傳回訊息。在此之後,驗證會傳回 `nil`。
旋轉金鑰
MessageEncryptor
也支援替換舊設定,方法是使用一組加密器進行遞迴。呼叫 rotate
建立並加入一個加密器,讓 decrypt_and_verify
也會嘗試遞迴。
預設情況下,任何旋轉的加密器都會使用主要加密器的值,除非另有指定。
您可以提供新的預設值給您的加密器
crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
然後,逐漸替換掉舊值,方法是將它們加入為遞迴。任何使用舊值產生的訊息會一直運作,直到移除旋轉。
crypt.rotate old_secret # Fallback to an old secret instead of @secret.
crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
不過,如果密碼和密文同時變更,上述程式碼應合併為
crypt.rotate old_secret, cipher: "aes-256-cbc"
- D
- E
- K
- N
常數
OpenSSLCipherError | = | OpenSSL::Cipher::CipherError |
共用方法
key_len(cipher = default_cipher) 連結
針對指定的密文,傳回密文的 key 長度,以幫助產生所需大小的 key
原始程式碼: 顯示 | 在 GitHub 上檢視
# File activesupport/lib/active_support/message_encryptor.rb, line 252 def self.key_len(cipher = default_cipher) OpenSSL::Cipher.new(cipher).key_len end
new(secret, sign_secret = nil, **options) 連結
初始化新的 MessageEncryptor
。secret
的長度必須至少與密文 key 長度相同。針對預設的「aes-256-gcm」密文,長度為 256 位元。如果您正在使用使用者輸入的 secret,您可以使用 ActiveSupport::KeyGenerator
或類似的 key 衍生函數來產生合適的 key。
第一個額外的參數用作 MessageVerifier
的簽章 key。這允許您指定 key 來加密和簽署資料。在使用「aes-256-gcm」等 AEAD 密文時會略過此參數。
ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
選項
:cipher
-
要使用的密文。它可以是
OpenSSL::Cipher.ciphers
傳回的任何密文。預設為「aes-256-gcm」。 :digest
-
用於簽章的
Digest
。在使用「aes-256-gcm」等 AEAD 密文時會略過此參數。 :serializer
-
<code>dump</code> 和 <code>load</code> 響應的序列化程式用於序列化訊息資料。您可指定任一物件,或從下列預先設定好的序列化程式中進行選擇:<code>:marshal</code>、<code>:json_allow_marshal</code>、<code>:json</code>、<code>:message_pack_allow_marshal</code>、<code>:message_pack</code>。
預先設定的序列化程式包含支援多重反序列化格式的退避機制。例如,<code>:marshal</code> 序列化程式會使用 <code>Marshal</code> 進行序列化,但可使用 <code>Marshal</code>、 <code>ActiveSupport::JSON</code> 或 <code>ActiveSupport::MessagePack</code> 進行反序列化。這可輕易地在序列化程式間進行轉移。
<code>:marshal</code>、<code>:json_allow_marshal</code> 和 <code>:message_pack_allow_marshal</code> 序列化程式支援使用 <code>Marshal</code> 進行反序列化,但其他序列化程式則不支援。要注意的是,在訊息簽名機密外洩時,<code>Marshal</code> 是反序列化攻擊的潛在媒介。若情況許可,請選擇不支援 <code>Marshal</code> 的序列化程式。
<code>:message_pack</code> 和 <code>:message_pack_allow_marshal</code> 序列化程式使用 <code>ActiveSupport::MessagePack</code>,其可往復 (Round-trip) 一些不受 <code>JSON</code> 支援的 Ruby 型別,並提供更佳的效能。不過,這些型別需要使用 <code>msgpack</code> gem。
在使用 Rails 時,預設值取決於 <code>config.active_support.message_serializer</code>。否則,預設值為 <code>:marshal</code>。
:url_safe
-
預設情況下,<code>MessageEncryptor</code> 會產生相符 RFC 4648 標準的字串,這些字串不符合 URL 安全原則。換言之,它們可能包含「+」和「/」。若要產生符合 URL 安全原則的字串(符合 RFC 4648 中的「使用 URL 和安全檔案名稱符號組進行 Base 64 編碼」),您可以傳遞 <code>true</code>。
:force_legacy_metadata_serializer
-
是否使用舊版的 metadata 序列化程式,其會先將訊息序列化,再將其封裝在也已序列化的信封中。此機制為 Rails 7.0 及以下版本的預設值。
如果您未傳遞 true 值,則預設值將使用 <code>config.active_support.use_message_serializer_for_metadata</code> 設定。
來源: 顯示 | 在 GitHub 上
# File activesupport/lib/active_support/message_encryptor.rb, line 183 def initialize(secret, sign_secret = nil, **options) super(**options) @secret = secret @cipher = options[:cipher] || self.class.default_cipher @aead_mode = new_cipher.authenticated? @verifier = if !@aead_mode MessageVerifier.new(sign_secret || secret, **options, serializer: NullSerializer) end end
實例公開方法
decrypt_and_verify(message, **options) 連結
解密並驗證一則訊息。我們需要驗證訊息以避免填充攻擊。參考: www.limited-entropy.com/padding-oracle-attacks/。
選項
:purpose
-
產生訊息所用的目的。若目的不相符,<code>decrypt_and_verify</code> 將會傳回 <code>nil</code>。
message = encryptor.encrypt_and_sign("hello", purpose: "greeting") encryptor.decrypt_and_verify(message, purpose: "greeting") # => "hello" encryptor.decrypt_and_verify(message) # => nil message = encryptor.encrypt_and_sign("bye") encryptor.decrypt_and_verify(message) # => "bye" encryptor.decrypt_and_verify(message, purpose: "greeting") # => nil
來源: 顯示 | 在 GitHub 上
# File activesupport/lib/active_support/message_encryptor.rb, line 241 def decrypt_and_verify(message, **options) catch_and_raise :invalid_message_format, as: InvalidMessage do catch_and_raise :invalid_message_serialization, as: InvalidMessage do catch_and_ignore :invalid_message_content do read_message(message, **options) end end end end
encrypt_and_sign(value, **options) 連結
加密並簽署一則訊息。我們需要簽署訊息以避免填充攻擊。參考: www.limited-entropy.com/padding-oracle-attacks/。
選項
:expires_at
-
訊息過期的日期時間。在這個日期時間之後,訊息驗證將會失敗。
message = encryptor.encrypt_and_sign("hello", expires_at: Time.now.tomorrow) encryptor.decrypt_and_verify(message) # => "hello" # 24 hours later... encryptor.decrypt_and_verify(message) # => nil
:expires_in
-
訊息有效期限的持續時間。在這個持續時間過後,訊息驗證將會失敗。
message = encryptor.encrypt_and_sign("hello", expires_in: 24.hours) encryptor.decrypt_and_verify(message) # => "hello" # 24 hours later... encryptor.decrypt_and_verify(message) # => nil
:purpose
-
訊息的目的。如果已指定,則驗證訊息時必須指定相同的目的;否則,會驗證失敗。(請參閱
decrypt_and_verify
。)