加密空字符串

Posted

技术标签:

【中文标题】加密空字符串【英文标题】:Encrypt empty string 【发布时间】:2016-05-06 06:19:33 【问题描述】:

我正在使用 Ruby 的 Open SSL bindings 进行 AES-256 加密。我可以加密一个非空字符串。但是,当尝试加密一个空字符串时,Ruby 会引发一个异常,抱怨数据不能为空。 如何使用 Ruby 的 OpenSSL 绑定?

重现问题的代码

require "openssl"

KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key

def encrypt(plaintext)
  cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
  cipher.encrypt
  iv = cipher.random_iv
  cipher.iv = iv
  cipher.key = KEY
  ciphertext = cipher.update(plaintext)    # <- ArgumentError here
  ciphertext << cipher.final
  [iv, ciphertext]
end

def decrypt(iv, ciphertext)
  cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
  cipher.decrypt
  cipher.iv = iv
  cipher.key = KEY
  plaintext = cipher.update(ciphertext)
  plaintext << cipher.final
  plaintext
end

p decrypt(*encrypt("foo"))    # "foo"

p decrypt(*encrypt(""))
# /tmp/foo.rb:11:in `update': data must not be empty (ArgumentError)
#         from /tmp/foo.rb:11:in `encrypt'
#         from /tmp/foo.rb:27:in `<main>'

版本

ruby-2.2.2p95 OpenSSL::VERSION 为“1.1.0” Microsoft SQL Server 2014 (12.0.2000.8)

为什么要?

我正在编写一个ETL 程序来将数据从一个数据库迁移到一个SqlServer 数据库。在将源数据库中的某些列写入目标数据库之前,必须对其进行加密。源列可以包含任何数据,包括空字符串。目标列通常不可为空。目标列将由 .net 代码解密。

目标 #1:没有任何关于加密字段的信息,包括它是否存在,在没有正确解密的情况下是不可恢复的。加密的空字符串应该与任何其他加密数据无法区分。

目标 #2:将解密这些值的 .net 代码不需要专门处理空字符串。

如果我可以让 openssl ,我将实现这两​​个目标。

解决方法 - 不要

我不能,将它们传递出去。

def encrypt(plaintext)
  return plaintext if plaintext.empty?
  ...
end

def decrypt(iv, ciphertext)
  return ciphertext if ciphertext.empty?
  ...
end

这有暴露信息的缺点,并且还需要在.net端编写协作代码。

解决方法 - 在明文中添加一些常量

我可以在加密之前在明文中添加一些常量字符串,并在解密后将其删除:

PLAINTEXT_SUFFIX = " "

def encrypt(plaintext)
  plaintext += PLAINTEXT_SUFFIX
  ...
end

def decrypt(iv, ciphertext)
  ...
  plaintext.chomp(PLAINTEXT_SUFFIX)
end

这隐藏了数据是否存在,但仍然需要配合.net代码。

【问题讨论】:

OpenSSL 支持 PKCS#7 填充,所以这应该可以工作。这可能更适合作为错误报告。顺便说一句,如果明文为空,您是否尝试过简单地跳过cipher.update?在这种情况下,cipher.final 可能就足够了。 @ArtjomB。我曾尝试省略对 Cipher#update 的调用。由于 ID10T 错误,当我早些时候尝试它时它没有工作,而不是我当时没有注意到。原来这就是解决方案。您介意将其添加为答案吗? 您可以自己添加。我对红宝石一无所知,我不想在这个特定的时间开始学习。 :) 【参考方案1】:

As suggested by @ArtjomB,就像不使用空字符串调用Cipher#update 一样简单。 Cipher#final 返回的值会正确加密一个空字符串。

require "openssl"

KEY = OpenSSL::Cipher::Cipher.new("aes-256-cbc").random_key

def encrypt(plaintext)
  cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
  cipher.encrypt
  iv = cipher.random_iv
  cipher.iv = iv
  cipher.key = KEY
  ciphertext = ""
  ciphertext << cipher.update(plaintext) unless plaintext.empty?
  ciphertext << cipher.final
  [iv, ciphertext]
end

def decrypt(iv, ciphertext)
  cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
  cipher.decrypt
  cipher.iv = iv
  cipher.key = KEY
  plaintext = cipher.update(ciphertext)
  plaintext << cipher.final
end

p decrypt(*encrypt("foo"))    # "foo"
p decrypt(*encrypt(""))       # ""

【讨论】:

【参考方案2】:

如果可以使用DBMS提供的加密功能,那么mysqlAES_ENCRYPT似乎可以加密空白字符串。

例如:

UPDATE some_table 
   SET some_column =  AES_ENCRYPT('',UNHEX('F3229A0B371ED2D9441B830D21A390C3'));

默认情况下它是 AES-128,我猜这将是一个问题,因为您需要 AES-256。另外,不确定您使用的是哪个 DBMS,以及该 DBMS 是否具有加密功能。

【讨论】:

我没想过用数据库来做加密。目标数据库是 SqlServer 2014。 @WayneConrad 不太了解该服务器,但它似乎支持 AES-256 加密。您可能需要对其进行空白字符串加密测试。 @WayneConrad 在旁注中,由于您的 SO 个人资料页面 - 我发现有一个名为 Ruby Extensions 的东西有 Symbol#to_proc 的第一个版本,它在 Rails 中被同化,然后被同化到 Ruby核。我正在调查你是否真的在 2003 年使用过[day, dollar].each(&amp;:another)。:D 优秀 抓住。 2003 年,我使用(我认为)Ruby 1.6。为了准确起见,我更新了我的个人资料。

以上是关于加密空字符串的主要内容,如果未能解决你的问题,请参考以下文章

java字串加密及String的各类函数说明

ios 常用的正则表达式(手机号邮箱md5加密验证空字符串等)

课后作业之字串加密

字串加密

使用md5怎么给一串字符串加密

字串加密