Active Support Message Encryptor¶ ↑
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)
然后,在验证时必须传递相同的 purpose 才能取回数据。
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
同样,如果一条消息没有 purpose,在通过特定 purpose 进行验证时将不会返回。
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) Link
给定一个加密算法,返回该加密算法的密钥长度,以帮助生成所需大小的密钥。
new(secret, sign_secret = nil, **options) Link
初始化一个新的 MessageEncryptor。secret 的长度必须至少等于加密算法的密钥大小。对于默认的 ‘aes-256-gcm’ 加密算法,这是 256 位。如果你使用的是用户输入的密钥,你可以通过 ActiveSupport::KeyGenerator 或类似的密钥派生函数来生成合适的密钥。
第一个附加参数用作 MessageVerifier 的签名密钥。这允许你指定密钥来加密和签名数据。在使用 AEAD 加密算法(如 ‘aes-256-gcm’)时会被忽略。
ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
选项¶ ↑
:cipher-
要使用的加密算法。可以是
OpenSSL::Cipher.ciphers返回的任何算法。默认是 ‘aes-256-gcm’。 :digest-
用于签名的
Digest。在使用 AEAD 加密算法(如 ‘aes-256-gcm’)时会被忽略。 :serializer-
用于序列化消息数据的序列化器。你可以指定任何响应
dump和load的对象,或者从几个预配置的序列化器中选择::marshal,:json_allow_marshal,:json,:message_pack_allow_marshal,:message_pack。预配置的序列化器包括一个回退机制,以支持多种反序列化格式。例如,
:marshal序列化器将使用Marshal进行序列化,但可以使用Marshal、ActiveSupport::JSON或ActiveSupport::MessagePack进行反序列化。这使得在不同序列化器之间迁移变得容易。:marshal、:json_allow_marshal和:message_pack_allow_marshal序列化器支持使用Marshal进行反序列化,但其他则不支持。请注意,在消息签名密钥泄露的情况下,Marshal是反序列化攻击的潜在向量。如果可能,请选择不支持Marshal的序列化器。:message_pack和:message_pack_allow_marshal序列化器使用ActiveSupport::MessagePack,它可以 roundtrip 一些JSON不支持的 Ruby 类型,并可能提供更好的性能。但是,这需要msgpackgem。在使用 Rails 时,默认值取决于
config.active_support.message_serializer。否则,默认值为:marshal。 :url_safe-
默认情况下,
MessageEncryptor生成符合 RFC 4648 的字符串,这些字符串不是 URL 安全的。换句话说,它们可能包含 “+” 和 “/”。如果你想生成 URL 安全的字符串(符合 RFC 4648 中的“Base 64 Encoding with URL and Filename Safe Alphabet”),你可以传入true。 :force_legacy_metadata_serializer-
是否使用旧的元数据序列化器,它会先序列化消息,然后将其包装在一个也经过序列化的信封中。这是 Rails 7.0 及更早版本的默认设置。
如果你不传递一个真值,默认值将通过
config.active_support.use_message_serializer_for_metadata设置。
# 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) Link
解密和验证消息。我们需要验证消息以避免填充攻击。参考:www.limited-entropy.com/padding-oracle-attacks/。
Options¶ ↑
:purpose-
消息生成时使用的 purpose。如果 purpose 不匹配,
decrypt_and_verify将返回nil。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
# 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) Link
加密和签名消息。我们需要签名消息以避免填充攻击。参考:www.limited-entropy.com/padding-oracle-attacks/。
Options¶ ↑
: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-
消息的 purpose。如果指定,则在验证消息时必须指定相同的 purpose;否则,验证将失败。(参见
decrypt_and_verify。)