Ruby Ruby 如何实现已知 C# 方式给出的 RSA 公钥的加密?

saillee · 2013年03月01日 · 最后由 saillee 回复于 2014年03月29日 · 6437 次阅读

项目中遇到一个问题,已知一个 C# 项目中的公钥,形式如下:

//Public key XML string
string publickey = "<RSAKeyValue><Modulus>ma+lXgSUOx73xNJWgC18G6GKYlERmAnZ1BQQPyaZ4mlzbi3A+4"
                               +"FKTWsGlHqWFG1d6+Gvb1RW6NB3yB4d5CsJRkV3CEbotxT/3UC"
                               +"cV3txebo+w2BIqtv/qTRXgaxygR1i0=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";

下面是我写的一个以此为公钥的加密程序:

require 'openssl'
require 'base64'
require 'xml/libxml'

def rsa_encrypt(public_key, message_string)
  Base64.encode64(public_key.public_encrypt(message_string.encode('UTF-16LE'))).rstrip
end

# @param xml_string [Object]
# @return [Object]
def rsa_public_key(xml_string)
  d = XML::Parser.string(xml_string).parse
  m = Base64.decode64(d.find_first('Modulus').content).unpack('H*')
  e = Base64.decode64(d.find_first('Exponent').content).unpack('H*')

  pub_key = OpenSSL::PKey::RSA.new
  # modulues
  pub_key.n = OpenSSL::BN.new m[0].hex.to_s
  # exponent
  pub_key.e = OpenSSL::BN.new e[0].hex.to_s
  # return Public Key
  pub_key
end

但在调试中加密结果一直不对,这个问题出在哪里?请高手支招!

应该还是字符编码的问题,c# 中字符串是 unicode 编码(但不是 utf-8),而 ruby1.9 中字符串编码是字符序列 + 编码,通常指定或转换编码为 UTF-8. 所以我建议你打印一下字符串序列和编码方式,及公钥和加密的数据(注意:打印数据的时候,以字节 8bit 的 16 进制数字输出)

你那个 publickey 不像 xml 啊

#2 楼 @luikore 对不起,系统把<>过滤掉了,已经修改过了,请指教。

#1 楼 @limpid 编码问题我已经考虑,对 message_string 进行了 UTF-16LE 格式的 encoding。按我 google 的资料来说 rsa_public_key 部分应该是没问题了(但不是十分肯定),估计是 rsa_encrypt 部分? 其实这个是用 ruby 来重写下面的 C# 代码:

/// <summary>
/// RSA加密
/// </summary>
/// <param name="xmlPublicKey">公钥</param>
/// <param name="m_strEncryptString"></param>
/// <returns>RSA公钥加密后的数据</returns>
public static string RSAEncrypt(string xmlPublicKey, string m_strEncryptString)
{
    string strEncry="";
    try
    {
        RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
        provider.FromXmlString(xmlPublicKey);
        byte[] bytes = new UnicodeEncoding().GetBytes(m_strEncryptString);
        strEncry = Convert.ToBase64String(provider.Encrypt(bytes, false));
    }
    catch (Exception exception)
    {
        throw exception;
    }
    return strEncry;
}

#3 楼 @saillee

no need to unpack

...
  m = Base64.decode64(d.find_first('Modulus').content)
  e = Base64.decode64(d.find_first('Exponent').content)

  pub_key = OpenSSL::PKey::RSA.new
  # modulus
  pub_key.n = OpenSSL::BN.new m, 2
  # exponent
  pub_key.e = OpenSSL::BN.new e, 2
...

@saillee ,rsa_encrypt 不会有问题的,因为这个是标准的,就是说对要加密的数据转换一定是一模一样的。不过,加密时的参数要注意,要设置好。另外就是加密数据时,输入的数据格式,或者输出的格式要求可能不同,所以我建议你打印一下数据格式,最好用你解密时的语言也做个加密的,跟 ruby 加密的比较一下。我在公司以前用 c++(windows 上)写的 RSA 加密的数据,发送给一个用 php(linux 上)写的网关,解密都没问题。另外当时加密的数据,在 c# 中也用到了,都没问题。不过,跟 php 联调的时候,出了点小问题,好像是加密过程中,对剩余的数据块长度不够时,如何处理,是可以通过参数设置的。因为加解密部分,我开始是在 linux 上用 c 写好并测试正确,才移植到 windows 上的。所以后来 php 那边,改动了个解密的参数就好了。

👍 谢谢 @luikore@limpid 两位热心给出的 tips,程序正在修改中,测试好后再给大家汇报!

#5 楼 @luikore 经测试,你提供的方法与我之前代码生成 pub_key 一致,当然你的方法更简洁一点。这个也同时印证了我关于 “rsa_public_key 部分应该是没问题了” 的想法。

难道 rsa_encrypt 上真的存在问题?!头有点大啊!

#8 楼 @saillee 只能一步步看区别是什么了...

对比 public key: 如果 C# 可以把 public key 用 pem 的格式输出, 在 ruby 中也输出一个 pem (pub_key.to_pem), 可以对比一下是否一样...

对比加密前的消息: 在 C# 中把 bytes 打出来, 在 ruby 中把message_string.encode('UTF-16LE').unpack('c*') 打出来, 对比一样是否也一样...

#9 楼 @luikore 今天在 https://superdry.apphb.com/tools/online-rsa-key-converter 这个网站上,我验证了我通过之前程序和你建议的写法所生成 PEM 格式的 public_key 的一致性,因此可以非常肯定 rsa_public_key 函数结果的正确性。因此,问题就集中在 rsa_encrypt 函数上了。

刚才测试了 rsa_public_key 中的 message_string.encode('UTF-16LE') 和 C# 中的 byte[] bytes = new UnicodeEncoding().GetBytes(m_strEncryptString); 两者的结果好像也没有什么不同,问题在哪呢?

问题解决了!不是出在密钥转化和加密程序上,而是出在我调用 WebService 的参数传递上,Soap4r 对于 xml 格式的参数进行转码!把其中的<>符号转成了&lt;&gt;,不给力啊!没办法就换成了 Savon 这个 gem,下面是重新整理后的代码:

require 'openssl'
require 'Base64'
require 'xml/libxml'

def rsa_encrypt(public_key, message_string)
  Base64.strict_encode64(public_key.public_encrypt(message_string.encode('UTF-16LE')))
  # 这个写法也是可以的
  #Base64.encode64(public_key.public_encrypt(message_string.encode('UTF-16LE'))).rstrip
end

# @param xml_string [Object]
# @return [Object]
def rsa_public_key(xml_string)
  d = XML::Parser.string(xml_string).parse
  m = Base64.decode64(d.find_first('Modulus').content)
  e = Base64.decode64(d.find_first('Exponent').content)

  pub_key = OpenSSL::PKey::RSA.new
  #modulues
  pub_key.n = OpenSSL::BN.new m, 2
  #exponent
  pub_key.e = OpenSSL::BN.new e, 2
  #return Public Key
  pub_key
end

# @param xml_string [Object]
# @return [Object]
def rsa_public_key1(xml_string)
  d = XML::Parser.string(xml_string).parse
  m = Base64.decode64(d.find_first('Modulus').content).unpack('H*')
  e = Base64.decode64(d.find_first('Exponent').content).unpack('H*')

  pub_key = OpenSSL::PKey::RSA.new
  #modulues
  pub_key.n = OpenSSL::BN.new m[0].hex.to_s
  #exponent
  pub_key.e = OpenSSL::BN.new e[0].hex.to_s
  #return Public Key
  pub_key
end

楼主你好! 我也遇到了这个问题,但是我用你这个方法报错,并且 require 'xml/libxml'这个 gem 包下载不下来你帮我看看吧: 公钥: publickey='y7Ad9XqKsKtpvb/ekTvZH3ZKER12iNdVZ/w11NLXGnLWohzqmVcFA5XBQSqzfCx+NeNa1AC7yLbIzwZpiiG62bzb9/aARQhSpcNxN5c1Xrps8gxbo4pOSJUZlqgLkGXfgz1ZuB3tCI9JEmJUmfXuXVC3lVL7x7b6g+nGaTe+Ob/r3nmsu/h5NGNtYUfMRBlSUsHMUWNoZGa4f6dERYmUgl1duLnZBsJP3hlHhLKuoXnl8cq1V7K7O4WqQ3m7mR1IXuYTZ4K9/tr6yu52Gic3PVRduURzspE+6lI9sUjLIsmIxVgJGFTbu3nRw1mb849RzkLPdidcmyx2C5lya89rBQ==AQAB'

c# 加密方法: private static string Encrypt(string originalString, string key) { try {《》 var RSA = new RSACryptoServiceProvider(); RSA.FromXmlString(key); byte[] dataToEncrypt = UnicodeEncoding.ASCII.GetBytes(originalString); byte[] encryptedData = RSA.Encrypt(dataToEncrypt, false); return Convert.ToBase64String(encryptedData); } catch (CryptographicException) { return string.Empty; } }

#13 楼 @lxj123 'xml/libxml'不是一个独立的 gem 包,这个是包含在了 soap4r-ruby 这个 gem 包中,如果你用的是 1.9 版本的 ruby 的话,要用 soap4r-ruby1.9 这个 gem。你可以安装后再试试。Gemfile 里面这样写:

gem 'soap4r-ruby1.9'

然后 bundle install 一下即可。希望能够帮到你。

saillee [该话题已被删除] 提及了此话题。 04月03日 10:58
需要 登陆 后方可回复, 如果你还没有账号请 注册新账号