.NET Core使用C#实现对接Java

与第三方对接最麻烦的是语言不同,因语言不同内置实现相关标准加密算法还是略微有所差异。

对接单点登录场景再寻常不过,由于时间紧迫且对接方使用Java,所以留给我对接开发和联调的时间本就不多,于是乎,在熬夜发版后,继而开始提前研究对接方所提供的加密方式大致处理。

数据对接加密算法采用RSA SHA1 1024位、同时呢,在Java中对于1024或其他位数,对密文有长度限制,所以利用了分段加密,密文长度为117,解密长度为128,如此通用处理方式,网上肯定是可以搜索到的,截取加密部分片段,如下:

.

public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
    byte[] keyBytes = Base64.getDecoder().decode(publicKey);
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
    Key publicK = keyFactory.generatePublic(x509KeySpec);
    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.ENCRYPT_MODE, publicK);
    int inputLen = data.length;
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    int offSet = 0;
    byte[] cache;
    int i = 0;
    while (inputLen - offSet > 0) {
        if (inputLen - offSet > 117) {
            cache = cipher.doFinal(data, offSet, 117);
        } else {
            cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * 117;
    }
    byte[] encryptedData = out.toByteArray();
    out.close();
    return encryptedData;
}

当然对于密钥在从证书中导出来肯定是二进制的,那么各个对接方最后转换为字符串方式有多种,比如base64、再比如转换为16进制等等,大同小异,这里就不展开了

了解完Java完整代码实现,我们需要对相关业务参数利用对接方所提供的公钥进行加密从而形成签名,以此请求时,将我们本地生成的公钥传递过去,在响应后进行验签,然后通过私钥解密

好了,讲到这里,我们假设已经本地生成证书,然后我们导出RSA公钥和私钥,伪代码如下:

var certificate = new X509Certificate2("pfx_path", "password", X509KeyStorageFlags.Exportable);
var rsa = certificate.GetRSAPrivateKey();
var privateKey = rsa.ExportRSAPrivateKey();
var publicKey = rsa.ExportRSAPublicKey();

那么我们如何利用对接方公钥进行加密呢?接下来在.NET Core中实现就需要解决上述加密算法出现的两个问题:
1,Java中可以通过RSA密钥(公钥或私钥)字符串转换为RSA密钥类,.NET Core提供了?
2,获取到RSA密钥类,调用对应实例doFinal方法进行分段加密,那么是否可以通过.NET Core中的RSA加密方法,也分段加密,结果是否一致?对接方通过公钥字符串加载RSA 公钥,代码如下:

public static RSAPublicKey loadPublicKeyByStr(String publicKeyStr) {
   byte[] buffer = Util.hexToByte(publicKeyStr);
   KeyFactory keyFactory = KeyFactory.getInstance("RSA");
   X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
   return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}

然后稍微查看了下加载类的解释
查看了下加载类的解释.png
所以我依图索骥,于是乎,大致有了如下模样

var servicePublicKey = "123456";
var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(Encoding.UTF8.GetBytes(servicePublicKey), out _);

好了,目前通过导入公钥主题信息貌似拿到了RSA,接下来则是分段加密,上述方法中cipher.doFinal,应该是指定算法实例的加密处理,那我们首先截取对应数据的分段,然后调用rsa的Encrypt方法,那加密填充模式是否一致,不得而知,默认如下提供为PKCS1?

var servicePublicKey = "123456";
var plainTextData = Encoding.UTF8.GetBytes("jeffcky");
var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(Encoding.UTF8.GetBytes(servicePublicKey), out _);
var outputStream = new MemoryStream();
var inputLen = plainTextData.Length;
int offSet = 0;
int i = 0;
while (inputLen - offSet > 0)
{
    Span<byte> bytes = plainTextData;
    byte[] encryptData;
    if (inputLen - offSet > 117)
    {
        Span<byte> slicedBytes = bytes.Slice(start: offSet, length: 117);
        encryptData = rsa.Encrypt(slicedBytes.ToArray(), RSAEncryptionPadding.Pkcs1);
    }
    else
    {
        Span<byte> slicedBytes = bytes.Slice(start: offSet, length: inputLen - offSet);
        encryptData = rsa.Encrypt(slicedBytes.ToArray(), RSAEncryptionPadding.Pkcs1);
    }
    outputStream.Write(encryptData, 0, encryptData.Length);
    i++;
    offSet = i * 117;
}

一顿不知其结果的操作后,经调用对接方接口,响应验签失败,反复改了几版后,依然失败。接下来就试试:使用IKVM实现对接Java