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