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) 链接
给定一个密码,返回密码的密钥长度,以帮助生成所需大小的密钥
来源:显示 | 在 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
必须至少与密码密钥大小一样长。对于默认的 ‘aes-256-gcm’ 密码,这是 256 位。如果您使用的是用户输入的密钥,您可以使用 ActiveSupport::KeyGenerator
或类似的密钥派生函数生成合适的密钥。
第一个附加参数用作 MessageVerifier
的签名密钥。这允许您指定用于加密和签名数据的密钥。在使用像 ‘aes-256-gcm’ 这样的 AEAD 密码时忽略。
ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
选项
:cipher
-
要使用的密码。可以是
OpenSSL::Cipher.ciphers
返回的任何密码。默认值为 ‘aes-256-gcm’。 :digest
-
Digest
用于签名。在使用像 ‘aes-256-gcm’ 这样的 AEAD 密码时忽略。 :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
,它可以对一些JSON
不支持的 Ruby 类型进行往返转换,并且可能提供更好的性能。但是,这些需要msgpack
gem。在使用 Rails 时,默认值取决于
config.active_support.message_serializer
。否则,默认值为:marshal
。 :url_safe
-
默认情况下,
MessageEncryptor
生成符合 RFC 4648 的字符串,这些字符串不是 URL 安全的。换句话说,它们可能包含“+”和“/”。如果您想生成 URL 安全的字符串(符合 RFC 4648 中的“使用 URL 和文件名安全的字母表的 Base 64 编码”),您可以传递true
。 :force_legacy_metadata_serializer
-
是否使用旧版元数据序列化器,该序列化器首先序列化消息,然后将其包装在一个也进行序列化的信封中。这是 Rails 7.0 及以下版本的默认值。
如果您不传递真值,则默认值将使用
config.active_support.use_message_serializer_for_metadata
设置。
来源:显示 | 在 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
-
消息生成的用途。如果用途不匹配,
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
来源:显示 | 在 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
。)
来源:显示 | 在 GitHub 上
# File activesupport/lib/active_support/message_encryptor.rb, line 220 def encrypt_and_sign(value, **options) create_message(value, **options) end