C++与Java之RSA签名与验签

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">      最近一段时间一直被一个事情困扰:支付相关RSA签名与验证签名,服务器使用java,客户端是c++的程序,在C++端验证签名的时候,试用了很多方法都无法签名通过。在java中,签名和验证签名很容易调用现有的类实现,但是在c++中却是不太容易。</span>

     采用openssl原生的c++程序,不行;

     在网上搜索了很久,也翻墙google了,试用了很多,也不行;

     用了其他网友借鉴PHP的代码的,也不行;

     。。。

     所有RSA流程总规则:私钥签名,公钥验签

     最后,在参考了一个貌似支付宝验证的alipay.h的文件后,今天终于通过验证签名,现把代码贴出,以便以后再有用,也对遇到同等类型的问题的朋友有个参考。

    JAVA端:

     

/**
	 * 用私钥对信息生成数字签名
	 * 
	 * @param data
	 *            加密数据
	 * @param privateKey
	 *            私钥
	 * 
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] data, String privateKey) throws Exception {
		// 解密由base64编码的私钥
		byte[] keyBytes = decryptBASE64(privateKey);

		// 构造PKCS8EncodedKeySpec对象
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);

		// KEY_ALGORITHM 指定的加密算法
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);

		// 取私钥匙对象
		PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);

		// 用私钥对信息生成数字签名
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(priKey);
		signature.update(data);

		return encryptBASE64(signature.sign());
	}

	/**
	 * 校验数字签名
	 * 
	 * @param data
	 *            加密数据
	 * @param publicKey
	 *            公钥
	 * @param sign
	 *            数字签名
	 * 
	 * @return 校验成功返回true 失败返回false
	 * @throws Exception
	 * 
	 */
	public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {

		// 解密由base64编码的公钥
		byte[] keyBytes = decryptBASE64(publicKey);

		// 构造X509EncodedKeySpec对象
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

		// KEY_ALGORITHM 指定的加密算法
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);

		// 取公钥匙对象
		PublicKey pubKey = keyFactory.generatePublic(keySpec);

		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(pubKey);
		signature.update(data);

		// 验证签名是否正常
		return signature.verify(decryptBASE64(sign));
	}

     看着很简单吧(不纠结于RSA实现过程)

     PHP端也很好找,不在贴部分代码,网上很多;

     通过上面JAVA代码通过私钥签名的字符串,如何通过c++验证签名的正确呢?

     先把公私钥的openssl生成过程,给贴出来(我是linux服务器测试的,终端中输入openssl):

     

OpenSSL> genrsa -out rsa_private_key.pem   1024  #生成私钥
OpenSSL> pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out rsa_private_key_pkcs8.pem #Java开发者需要将私钥转换成PKCS8格式
OpenSSL> rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem #生成公钥
OpenSSL> exit #退出OpenSSL程序

     JAVA签名/验证签名测试:

public static void main(String[] args) throws Exception {
		// 签名生成
		String content = "appId=23232323&&testestesjfijfe12";
		
		String privateKey = "mbyKNDk+fpYHGZ8WMzGJjA6wWsFcWDxOtdIP4BR7W00Shvau/QJBALQxheLcK9s3CfnD+RtQK9MxKbk/oe0Pjf+UvmufUJOWzGNzThuwNA70EThKb0VBNMaXbeHxVicU0QquTdKQkH0CQG/VwLy00QjqwLv6oqZ+i6XpsSoCTlwe25Yp/pjsUrpq5+DnZ9mkw2s2WUi2sdwOpUogctQ5XlBbdjOLpoLhVjM=";
		String sign = sign(content.getBytes(), privateKey);
		String lastSign = URLEncoder.encode(sign.replace("\n", ""), "UTF-8");
		System.out.println("签名内容:" + content);
		System.out.println("最终签名:" + lastSign);

		// 签名验证
		String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA";
		boolean bverify = verify(content.getBytes(), publicKey, URLDecoder.decode(xiaoySign, "UTF-8"));
		
		System.out.println("验证结果:" + bverify +";decode sign="+URLDecoder.decode(xiaoySign, "UTF-8"));

	}

     c++实现,直接代码:

    工具类中,需要工具:

   

        static std::string url_encode(const std::string& szToEncode);
	static std::string url_decode(const std::string& szToDecode);
	static bool verify_rsa(RSA *rsa ,const std::string &content, const std::string &sign);
实现:

std::string common_tool::url_encode(const std::string& szToEncode)
{
    std::string src = szToEncode;
    char hex[] = "0123456789ABCDEF";
    std::string dst;

    for (size_t i = 0; i < src.size(); ++i)
    {
        unsigned char cc = src[i];
        if (isascii(cc))
        {
            if (cc == ' ')
            {
                dst += "%20";
            }
            else
                dst += cc;
        }
        else
        {
            unsigned char c = static_cast<unsigned char>(src[i]);
            dst += '%';
            dst += hex[c / 16];
            dst += hex[c % 16];
        }
    }
    return dst;
}

std::string common_tool::url_decode(const std::string &SRC) {
	std::string ret;
	char ch;
	int i, ii;
	for (i=0; i<SRC.length(); i++) {
			if (int(SRC[i])==37) {
					sscanf(SRC.substr(i+1,2).c_str(), "%x", &ii);
					ch=static_cast<char>(ii);
					ret+=ch;
					i=i+2;
			} else {
					ret+=SRC[i];
			}
	}
	return (ret);
}
bool common_tool::verify_rsa(/*const char *public_key*/RSA *rsa ,
                        const std::string &content, const std::string &sign) {
	BIO *bufio = NULL;
	EVP_PKEY *evpKey = NULL;
	bool verify = false;
	EVP_MD_CTX ctx;
	int result = 0;
	std::string decodedSign = common_tool::base64_decode(sign);
	char *chDecodedSign = const_cast<char*>(decodedSign.c_str());

	if (rsa == NULL) {
			printf("PEM_read_bio_RSA_PUBKEY failed");
			goto safe_exit;
	}

	evpKey = EVP_PKEY_new();
	if (evpKey == NULL) {
			printf("EVP_PKEY_new failed");
			goto safe_exit;
	}

	if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {
			printf("EVP_PKEY_set1_RSA failed");
			goto safe_exit;
	}

	EVP_MD_CTX_init(&ctx);

	if (result == 1 && (result = EVP_VerifyInit_ex(&ctx,
									EVP_md5(), NULL)) != 1) {
			printf("EVP_VerifyInit_ex failed");
	}

	if (result == 1 && (result = EVP_VerifyUpdate(&ctx,
									content.c_str(), content.size())) != 1) {
			printf("EVP_VerifyUpdate failed");
	}

	if (result == 1 && (result = EVP_VerifyFinal(&ctx,
									(unsigned char*)chDecodedSign,
									decodedSign.size(), evpKey)) != 1) {
			printf("EVP_VerifyFinal failed");
	}
	if (result == 1) {
			verify = true;
	} else {
			printf("verify failed");
	}

	EVP_MD_CTX_cleanup(&ctx);

	safe_exit:
	if (rsa != NULL) {
			RSA_free(rsa);
			rsa = NULL;
	}

	if (evpKey != NULL) {
			EVP_PKEY_free(evpKey);
			evpKey = NULL;
	}

	if (bufio != NULL) {
			BIO_free_all(bufio);
			bufio = NULL;
	}

	return verify;
}

其中,

verify_rsa 是整个验证的核心。

参数准备:

RSA *rsa

RSA

  BIO *key = NULL; 
        RSA *r = NULL; 
        key = BIO_new(BIO_s_file()); 
        BIO_read_filename(key, "rsa_public_key.pem"); 
        r = PEM_read_bio_RSAPublicKey(key, NULL, NULL, NULL); 
        BIO_free_all(key);

content为加密的字符串

sign为java生成的签名,java中这个签名urlencode了,传入之前,需要先urldecode一下。

在此函数中,sign还需要base64 decode一下,这样就没问题了。


时间有限,不多写了。

把c++签名过程再贴一下:

static std::string sign(const char *private_key, 
			const std::string &content) {
		BIO *bufio = NULL;
		RSA *rsa = NULL;
		EVP_PKEY *evpKey = NULL;
		bool verify = false;
		EVP_MD_CTX ctx;
		int result = 0;
		unsigned int size = 0;
		char *sign = NULL;
		std::string signStr = "";

		//bufio = BIO_new_mem_buf((void*)private_key, -1);
		//if (bufio == NULL) {
		//	ERR("BIO_new_mem_buf failed");
		//	goto safe_exit;
		//}
		bufio = BIO_new(BIO_s_file());
                BIO_read_filename(bufio, "rsa_private_key_pkcs8.pem");
                //BIO_read_filename(bufio, "rsa_private_key.pem");

		rsa = PEM_read_bio_RSAPrivateKey(bufio, NULL, NULL, NULL);
		if (rsa == NULL) {
			ERR("PEM_read_bio_RSAPrivateKey failed");
			goto safe_exit;
		}

		evpKey = EVP_PKEY_new();
		if (evpKey == NULL) {
			ERR("EVP_PKEY_new failed");
			goto safe_exit;
		}

		if ((result = EVP_PKEY_set1_RSA(evpKey, rsa)) != 1) {
			ERR("EVP_PKEY_set1_RSA failed");
			goto safe_exit;
		}

		EVP_MD_CTX_init(&ctx);

		if (result == 1 && (result = EVP_SignInit_ex(&ctx, 
						EVP_md5(), NULL)) != 1) {
			ERR("EVP_SignInit_ex failed");
		}

		if (result == 1 && (result = EVP_SignUpdate(&ctx, 
						content.c_str(), content.size())) != 1) {
			ERR("EVP_SignUpdate failed");
		}

		size = EVP_PKEY_size(evpKey);
		sign = (char*)malloc(size+1);
		memset(sign, 0, size+1);
		
		if (result == 1 && (result = EVP_SignFinal(&ctx, 
						(unsigned char*)sign,
						&size, evpKey)) != 1) {
			ERR("EVP_SignFinal failed");
		}

		if (result == 1) {
			verify = true;
		} else {
			ERR("verify failed");
		}

		signStr = common_tool::base64_encode((const unsigned char*)sign, size);
		EVP_MD_CTX_cleanup(&ctx);
		free(sign);

safe_exit:
		if (rsa != NULL) {
			RSA_free(rsa);
			rsa = NULL;
		}

		if (evpKey != NULL) {
			EVP_PKEY_free(evpKey);
			evpKey = NULL;
		}

		if (bufio != NULL) {
			BIO_free_all(bufio);
			bufio = NULL;
		}

		return signStr;
		//return sign;
	}

sign的过程,如果需要和verify一样,需要改一下传入参数。


参考:


猜你喜欢

转载自blog.csdn.net/richerg85/article/details/51723124