因为业务需要,与第三方对接时,第三方签名方法居然采用的ECDsa
,而不是更常见的RSA
、MD5
之类,真是不走寻常路,当然我是不会承认是我见识太少的!!!
麻利的让对方提供了签名算法代码,奈何对方是java
,提供的也是java
版本代码,具体代码如下:
/**
* Project Name:trustsql_sdk
* File Name:ECDSAAlgoUtil.java
* Package Name:com.tencent.trustsql.sdk.algo
* Date:Jul 26, 20175:17:04 PM
* Copyright (c) 2017, Tencent All Rights Reserved.
*
*/
package com.tencent.trustsql.sdk.algorithm;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DERSequenceGenerator;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.spongycastle.asn1.ASN1InputStream;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequenceGenerator;
import org.spongycastle.asn1.DLSequence;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.ec.CustomNamedCurves;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.FixedPointUtil;
import com.google.common.base.Objects;
import com.tencent.trustsql.sdk.Constants;
/**
*
* @author sagazhang
*
*/
public class ECDSAAlgorithm {
public static final ECDomainParameters CURVE;
public static final BigInteger HALF_CURVE_ORDER;
static {
X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
// Tell Bouncy Castle to precompute data that's needed during secp256k1
// calculations. Increasing the width
// number makes calculations faster, but at a cost of extra memory usage
// and with decreasing returns. 12 was
// picked after consulting with the BC team.
FixedPointUtil.precompute(CURVE_PARAMS.getG(), 12);
CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(),
CURVE_PARAMS.getH());
HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1);
}
public static String generatePrivateKey() throws Exception {
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance(Constants.RANDOM_NUMBER_ALGORITHM,
Constants.RANDOM_NUMBER_ALGORITHM_PROVIDER);
} catch (Exception e) {
secureRandom = new SecureRandom();
}
// Generate the key, skipping as many as desired.
byte[] privateKeyAttempt = new byte[32];
secureRandom.nextBytes(privateKeyAttempt);
BigInteger privateKeyCheck = new BigInteger(1, privateKeyAttempt);
while (privateKeyCheck.compareTo(BigInteger.ZERO) == 0 || privateKeyCheck.compareTo(Constants.MAXPRIVATEKEY) == 1) {
secureRandom.nextBytes(privateKeyAttempt);
privateKeyCheck = new BigInteger(1, privateKeyAttempt);
}
// System.out.println(Hex.encodeHexString(privateKeyAttempt));
String result = Base64.encodeBase64String(privateKeyAttempt);
result = result.replaceAll("[\\s*\t\n\r]", "");
return result;
}
public static String generatePublicKey(String priateKeyBase64String, boolean encode) throws Exception {
try {
byte[] privateKeyBytes = Base64.decodeBase64(priateKeyBase64String);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint pointQ = spec.getG().multiply(new BigInteger(1, privateKeyBytes));
String result = Base64.encodeBase64String(pointQ.getEncoded(encode));
result = result.replaceAll("[\\s*\t\n\r]", "");
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static String generatePublicKey(String priateKeyBase64String) throws Exception {
return generatePublicKey(priateKeyBase64String, false);
}
public static String decodePublicKey(String encodePubKeyBase64String) throws Exception {
try {
byte[] encodePubkeyBytes = Base64.decodeBase64(encodePubKeyBase64String);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint pointQ = spec.getG().getCurve().decodePoint(encodePubkeyBytes);
String result = Base64.encodeBase64String(pointQ.getEncoded(false));
result = result.replaceAll("[\\s*\t\n\r]", "");
return result;
} catch(Exception e) {
throw new RuntimeException(e);
}
}
public static String getAddress(byte[] keyBytes, int...version) throws Exception {
byte[] hashSha256 = BaseAlgorithm.encode("SHA-256", keyBytes);
MessageDigest messageDigest = MessageDigest.getInstance("RipeMD160");
messageDigest.update(hashSha256);
byte[] hashRipeMD160 = messageDigest.digest();
byte[] versionHashBytes = new byte[1 + hashRipeMD160.length];
if(version == null || version.length == 0) {
versionHashBytes[0] = 0;
} else {
versionHashBytes[0] = (byte) version[0];
}
System.arraycopy(hashRipeMD160, 0, versionHashBytes, 1, hashRipeMD160.length);
byte[] checkSumBytes = BaseAlgorithm.encodeTwice("SHA-256", versionHashBytes);
byte[] rawAddr = new byte[versionHashBytes.length + 4];
System.arraycopy(versionHashBytes, 0, rawAddr, 0, versionHashBytes.length);
System.arraycopy(checkSumBytes, 0, rawAddr, versionHashBytes.length, 4);
// byte[] hashRipeMD160 = messageDigest.digest();
// byte[] hashDoubleSha256 = BaseAlgorithm.encodeTwice("SHA-256", hashRipeMD160);
// byte[] rawAddr = new byte[1 + hashRipeMD160.length + 4];
// rawAddr[0] = 0;
// System.arraycopy(hashRipeMD160, 0, rawAddr, 1, hashRipeMD160.length);
// System.arraycopy(hashDoubleSha256, 0, rawAddr, hashRipeMD160.length + 1, 4);
return Base58Algorithm.encode(rawAddr);
}
public static String sign(String privateKey, byte[] data, boolean isHash256) throws Exception {
byte[] hash256 = data;
if(!isHash256) { //未hash256 encode的字节数组
hash256 = BaseAlgorithm.encode("SHA-256", data);
}
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
//ECDSASigner signer = new ECDSASigner();
BigInteger pri = new BigInteger(1, Base64.decodeBase64(privateKey));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(pri, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(hash256);
byte[] content = new ECDSASignature(components[0], components[1]).toCanonicalised().encodeToDER();
String result = Base64.encodeBase64String(content);
result = result.replaceAll("[\\s*\t\n\r]", "");
return result;
}
public static String sign(String privateKey, byte[] data) throws Exception {
byte[] hash256 = data;
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
BigInteger pri = new BigInteger(1, Base64.decodeBase64(privateKey));
ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(pri, CURVE);
signer.init(true, privKey);
BigInteger[] components = signer.generateSignature(hash256);
byte[] content = new ECDSASignature(components[0], components[1]).toCanonicalised().encodeToDER();
String result = Base64.encodeBase64String(content);
result = result.replaceAll("[\\s*\t\n\r]", "");
return result;
}
public static boolean verify(String srcStr,String sign, String pubKey) throws Exception {
byte[] hash256 = BaseAlgorithm.encode("SHA-256", srcStr.getBytes("UTF-8"));
ECDSASignature eCDSASignature = ECDSASignature.decodeFromDER(Base64.decodeBase64(sign));
ECDSASigner signer = new ECDSASigner();
org.spongycastle.math.ec.ECPoint pub = CURVE.getCurve().decodePoint(Base64.decodeBase64(pubKey));
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub.getEncoded()), CURVE);
signer.init(false, params);
return signer.verifySignature(hash256, eCDSASignature.r, eCDSASignature.s);
}
public static boolean verify(String srcStr,boolean isHash256, String sign, String pubKey) throws Exception {
byte[] hash256 = srcStr.getBytes("UTF-8");
if(!isHash256) {
hash256 = BaseAlgorithm.encode("SHA-256", srcStr.getBytes("UTF-8"));
}
ECDSASignature eCDSASignature = ECDSASignature.decodeFromDER(Base64.decodeBase64(sign));
ECDSASigner signer = new ECDSASigner();
org.spongycastle.math.ec.ECPoint pub = CURVE.getCurve().decodePoint(Base64.decodeBase64(pubKey));
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub.getEncoded()), CURVE);
signer.init(false, params);
return signer.verifySignature(hash256, eCDSASignature.r, eCDSASignature.s);
}
public static boolean verify(byte[] bytes, boolean isHash256, String sign, String pubKey) throws Exception {
byte[] hash256 = bytes;
if(!isHash256) {
hash256 = BaseAlgorithm.encode("SHA-256", bytes);
}
ECDSASignature eCDSASignature = ECDSASignature.decodeFromDER(Base64.decodeBase64(sign));
ECDSASigner signer = new ECDSASigner();
org.spongycastle.math.ec.ECPoint pub = CURVE.getCurve().decodePoint(Base64.decodeBase64(pubKey));
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub.getEncoded()), CURVE);
signer.init(false, params);
return signer.verifySignature(hash256, eCDSASignature.r, eCDSASignature.s);
}
public static boolean VerifyRenSign(String pubKey, byte[] bytes, String sign) {
byte[] hash256 = bytes;
//byte[] hash256 = BaseAlgorithm.encode("SHA-256", bytes);
ECDSASignature eCDSASignature = ECDSASignature.decodeFromDER(Base64.decodeBase64(sign));
ECDSASigner signer = new ECDSASigner();
org.spongycastle.math.ec.ECPoint pub = CURVE.getCurve().decodePoint(Base64.decodeBase64(pubKey));
ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub.getEncoded()), CURVE);
signer.init(false, params);
return signer.verifySignature(hash256, eCDSASignature.r, eCDSASignature.s);
}
public static class ECDSASignature {
/** The two components of the signature. */
public final BigInteger r, s;
/**
* Constructs a signature with the given components. Does NOT
* automatically canonicalise the signature.
*/
public ECDSASignature(BigInteger r, BigInteger s) {
this.r = r;
this.s = s;
}
/**
* Returns true if the S component is "low", that means it is below
* {@link ECKey#HALF_CURVE_ORDER}. See <a href=
* "https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#Low_S_values_in_signatures">
* BIP62</a>.
*/
public boolean isCanonical() {
return s.compareTo(HALF_CURVE_ORDER) <= 0;
}
/**
* Will automatically adjust the S component to be less than or equal to
* half the curve order, if necessary. This is required because for
* every signature (r,s) the signature (r, -s (mod N)) is a valid
* signature of the same message. However, we dislike the ability to
* modify the bits of a Bitcoin transaction after it's been signed, as
* that violates various assumed invariants. Thus in future only one of
* those forms will be considered legal and the other will be banned.
*/
public ECDSASignature toCanonicalised() {
if (!isCanonical()) {
// The order of the curve is the number of valid points that
// exist on that curve. If S is in the upper
// half of the number of valid points, then bring it back to the
// lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are
// valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is
// canonical.
return new ECDSASignature(r, CURVE.getN().subtract(s));
} else {
return this;
}
}
/**
* DER is an international standard for serializing data structures
* which is widely used in cryptography. It's somewhat like protocol
* buffers but less convenient. This method returns a standard DER
* encoding of the signature, as recognized by OpenSSL and other
* libraries.
*/
public byte[] encodeToDER() {
try {
return derByteStream().toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public static ECDSASignature decodeFromDER(byte[] bytes) {
ASN1InputStream decoder = null;
try {
decoder = new ASN1InputStream(bytes);
DLSequence seq = (DLSequence) decoder.readObject();
if (seq == null)
throw new RuntimeException("Reached past end of ASN.1 stream.");
ASN1Integer r, s;
try {
r = (ASN1Integer) seq.getObjectAt(0);
s = (ASN1Integer) seq.getObjectAt(1);
} catch (ClassCastException e) {
throw new IllegalArgumentException(e);
}
// OpenSSL deviates from the DER spec by interpreting these
// values as unsigned, though they should not be
// Thus, we always use the positive versions. See:
// http://r6.ca/blog/20111119T211504Z.html
return new ECDSASignature(r.getPositiveValue(), s.getPositiveValue());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (decoder != null)
try {
decoder.close();
} catch (IOException x) {
}
}
}
protected ByteArrayOutputStream derByteStream() throws IOException {
// Usually 70-72 bytes.
ByteArrayOutputStream bos = new ByteArrayOutputStream(72);
DERSequenceGenerator seq = new DERSequenceGenerator(bos);
seq.addObject(new ASN1Integer(r));
seq.addObject(new ASN1Integer(s));
seq.close();
return bos;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ECDSASignature other = (ECDSASignature) o;
return r.equals(other.r) && s.equals(other.s);
}
@Override
public int hashCode() {
return Objects.hashCode(r, s);
}
}
}
好家伙,用的还是Tencent的sdk,而且一如RSA
一样,对方提供的公钥私钥果然还是base64
格式,根本无法应用于C#
下的ECDsaCng
,好在之前已经做过了大量BouncyCastle
相关的代码,虽然略有波折,但还是成功的将java
代码改造成了C#
代码,而过程中发现java
下的ASN1Integer
,实际与C#
下的DerInteger
对应,这样如果后续还有其它基于BouncyCastle
的跨语言对接,应该也可以减少一些对接中的阻碍
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.EC;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Signers;
using Org.BouncyCastle.Math;
using System.IO;
/// <summary>
/// ECDsa签名验证算法
/// </summary>
public static class ECDsaHelper
{
private static readonly ECDomainParameters Curve;
private static readonly BigInteger HalfCurveOrder;
static ECDsaHelper()
{
var curveParams = CustomNamedCurves.GetByName("secp256k1");
Curve = new ECDomainParameters(curveParams.Curve, curveParams.G, curveParams.N, curveParams.H);
HalfCurveOrder = curveParams.N.ShiftRight(1);
}
/// <summary>
/// 签名
/// </summary>
/// <param name="privateKey">ECDsa私钥</param>
/// <param name="content">待签名的内容</param>
/// <returns></returns>
public static byte[] SignData(byte[] privateKey, byte[] content)
{
//为了保证签名结果与java一致,此处参数同样传递了IDsaKCalculator,但测试发现如果不传虽然签名结果不一致,但验签是可以通过的
var signer = new ECDsaSigner(new HMacDsaKCalculator(new Sha256Digest()));
var priKey = new ECPrivateKeyParameters(new BigInteger(1, privateKey), Curve);
signer.Init(true, priKey);
var components = signer.GenerateSignature(content);
using (var stream = new MemoryStream())
{
var seq = new DerSequenceGenerator(stream);
seq.AddObject(new DerInteger(components[0]));
var s = components[1];
if (s.CompareTo(HalfCurveOrder) > 0)
{
s = Curve.N.Subtract(components[1]);
}
seq.AddObject(new DerInteger(s));
seq.Close();
var signature = stream.ToArray();
return signature;
}
}
/// <summary>
/// 验签
/// </summary>
/// <param name="publicKey">ECDsa公钥</param>
/// <param name="content">待验签的内容</param>
/// <param name="signature">待比较的签名结果</param>
/// <returns></returns>
public static bool VerifyData(byte[] publicKey, byte[] content, byte[] signature)
{
using (var input = new Asn1InputStream(signature))
{
var seq = (DerSequence)input.ReadObject();
if (seq != null)
{
var signer = new ECDsaSigner();
var point = Curve.Curve.DecodePoint(publicKey);
var pubKey = new ECPublicKeyParameters(Curve.Curve.DecodePoint(point.GetEncoded()), Curve);
signer.Init(false, pubKey);
return signer.VerifySignature(content, ((DerInteger)seq[0]).Value, ((DerInteger)seq[1]).Value);
}
}
return false;
}
}
就两个方法,也就不提供测试代码了,反正签名的逻辑是没看懂,只是依样画葫芦的抄了java
的逻辑。