【问题标题】:Blowfish encryption: Java vs. RubyBlowfish 加密:Java 与 Ruby
【发布时间】:2014-05-13 03:54:47
【问题描述】:

我正在尝试从 Puppet (Ruby) 生成一些加密的 JBoss 数据源密码。 JBoss 使用this library (see line 149) 来加密/解密密码(使用 Blowfish 和在 Java 代码中编译的预定义密钥)。

现在,我研究了一下,发现默认情况下,javax.crypto 对河豚使用以下参数:Blowfish/ECB/PKCS5Padding。

默认情况下,Ruby 的 OpenSSL 库根本不进行任何填充(如果它不是 8 的倍数,则简单地拒绝该键)。我尝试手动进行填充,这是我的完整代码:

#!/usr/bin/ruby

require 'openssl'

key = 'jaas is the way'

ciph = OpenSSL::Cipher.new('bf-ecb').encrypt
ciph.key = key + (8 - key.length % 8).chr * (8 - key.length % 8)

b = ciph.update(ARGV[0]) + ciph.final()
puts b.unpack('H*')

我尝试实现 PKCS5 填充的方式与我在 Python 中看到的类似(Python vs. Java Blowfish)问题中的实现方式相同,方法是添加 chr(直到长度为 8 的倍数的字节数)直到字符串的长度是 8 的倍数。对于这个特定的键,它应该恰好添加一个 chr(1),并且在尝试调试时,它似乎做得很好。

问题是我的 Ruby 代码给出的结果与 Java 中的 Picketbox 库中的代码获得的结果不同。

编辑: 以下是我在运行更多测试后得到的一些结论:

  • 如果要加密的密钥和明文都是16字节,结果没问题

  • 我们尝试使用 16 字节长的纯文本字符串和 15 字节密钥进行加密。然后我们用 0 到 255 的 ASCII 码的所有字符在 Ruby 中填充(前后)密钥,并且没有一个结果与我们在 Java 中获得的加密文本匹配。这根本没有意义,因为如果 Java 只是简单地用任何东西填充键,它就会在原始键的开头或结尾添加一个字符。唯一合乎逻辑的解释可能是 Java 确实通过其他方式(而不是附加字符)操作密钥字符串。

【问题讨论】:

  • 如果key是8的倍数,结果是否一样?
  • 不,结果不一样,很遗憾。即使在 Java 代码中我在初始化 Cipher 时放置了 Blowfish/ECB/NoPadding,结果也会更加尴尬,因为在这种情况下我使用 Java 得到的字符串更短,这让我认为 Ruby 也做了某种填充到一个计算值。
  • @UriAgassi,用户 Jasper 在该页面上发布的解决方案不起作用。首先是因为他使用空格来填充。如果我用我的填充方法替换他的填充方法,我会得到与我的代码完全相同的结果。此外,这不是同一个问题,因为就他的问题而言,除了最后一个块外,他一切都很好。在我的情况下,没有什么是好的,如果密钥在后台被 Java 以不同的方式使用/处理,这是有道理的。感谢您的提示。
  • 尝试填充明文数据...

标签: java ruby jboss puppet blowfish


【解决方案1】:

看起来 Java Blowfish 根本没有填充键;一旦到达密钥的末尾,它就会从第一个字节重新开始。 (尝试使用键“abc”,然后使用“abcabc”,看看会发生什么。)

我建议查看这个Crypt19 gem - 他们的 Blowfish 实现接受不是 8 的倍数的密钥。但他们只提供 CBC 模式,因此您需要自己实现 ECB。应该很容易 - 只是:

  • 填写信息(就像您如何填写按键一样)
  • 将消息分成 8 字节块,然后
  • 为每个区块调用encrypt_block

编辑:好的,这是代码。这会产生与 Java 版本相同的输出,至少对于这个“Hello, world!”是这样。输入。

require 'crypt/blowfish' 

secret = "Hello, world!"
blowfish = Crypt::Blowfish.new("jaas is the way")
padded_secret = secret + (8 - secret.length % 8).chr * (8 - secret.length % 8)
encrypted = padded_secret.scan(/.{8}/).map{|block| blowfish.encrypt_block(block).unpack('H*')}.join
puts encrypted #print "17a0aef80205dd34d08dbf27a0101c9c"

【讨论】:

  • 根据你的说法(这是有道理的,我过去见过类似的实现)我的关键应该是“jaas is the wayj”,最后的“j”是完成制作 128 位。使用 8 字节消息(以避免填充问题)和这个键,我仍然在 Ruby 和 Java 中得到不同的结果。事实上,如您所见,我在 Ruby 中生成了 256 个加密字符串,通过将 0 到 255 的所有字节附加到 15 字节的密钥中,它们都没有生成由 Java 代码生成的字符串,这让我想知道 Java 是否这样做对键或消息进行一些其他操作。
  • 不,你所描述的仍然是填充,这不是它的实现方式,至少根据 OpenJDK 版本(grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… - 请参阅 init 方法)。它根本没有对键进行任何操作,没有填充或其他任何东西,除了检查它是否太长。密钥不必是 8 的倍数长度。所以你不能使用 OpenSSL 实现。看看我的代码 - 它为我产生完全相同的输出。
  • 非常感谢。使用您的解决方案,我还使用 OpenSSL 库实现了它。
【解决方案2】:

Worakarn Isaratham 的回答是 100% 正确的,但是,我想使用标准 OpenSSL 库为这个问题提供一个实现:

#!/usr/bin/ruby

require 'openssl'

def JBossPasswordEncrypt(s)
    ciph = OpenSSL::Cipher.new('bf-ecb').encrypt

    # https://source.jboss.org/browse/PicketBox/trunk/security-jboss-sx/jbosssx/src/main/java/org/picketbox/datasource/security/SecureIdentityLoginModule.java?r=332 Line 153
    ciph.key_len = 15
    ciph.key = 'jaas is the way'

    # PKCS5 Padding
    s = s + (8 - s.length % 8).chr * (8 - s.length % 8)

    # Split the secret in chunks of 8 and encrypt it
    e = s.scan(/.{8}/).map { |block| ciph.update(block).unpack('H*') }

    # Convert the bytes array into a signed number
    en = e.join.to_i(16)
    en = en - (2 ** (en.size * 8)) if en >= (2 ** (en.size * 8 - 1))

    # Return the result in hex (as string)
    return en.to_s(16)
end

puts JBossPasswordEncrypt(ARGV[0])

非常感谢大家的帮助!

编辑:我添加了对有符号数字的转换,就像 Java 一样。不太好,但它有效。

编辑:我把它写成一个更容易理解语法和 cmets 的方法。

【讨论】:

  • 哦,我不知道你可以这样设置密钥长度。很高兴它对你有用!
猜你喜欢
  • 2018-12-23
  • 2019-09-09
  • 1970-01-01
  • 2012-01-22
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 2015-09-25
  • 1970-01-01
相关资源
最近更新 更多