rsa 具体是什么 这个就不在多说。算法实现啊应用啊 已经有很多了。今天记录下 这种特殊的需求,前台签名,后台验证
Java后台产生 密匙对
pom.xml 添加BC 依赖
org.bouncycastle bcprov-jdk15on 1.51
RSATools
package com.oscgc.securevideo.server.tool.rsa; import java.io.IOException; import java.io.StringWriter; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.util.io.pem.PemObject; /** * Created by Yq on 2015/6/10. */ public class RsaKeyTools { public static final String PEM_PUBLICKEY = "PUBLIC KEY"; public static final String PEM_PRIVATEKEY = "PRIVATE KEY"; /** * generateRSAKeyPair * * @param keySize * @return */ public static KeyPair generateRSAKeyPair(int keySize) { KeyPairGenerator generator = null; SecureRandom random = new SecureRandom(); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); try { generator = KeyPairGenerator.getInstance("RSA", "BC"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } generator.initialize(keySize, random); KeyPair keyPair = generator.generateKeyPair(); return keyPair; } /** * convertToPemKey * * @param publicKey * @param privateKey * @return */ public static String convertToPemKey(RSAPublicKey publicKey, RSAPrivateKey privateKey) { if (publicKey == null && privateKey == null) { return null; } StringWriter stringWriter = new StringWriter(); try { PEMWriter pemWriter = new PEMWriter(stringWriter, "BC"); if (publicKey != null) { pemWriter.writeObject(new PemObject(PEM_PUBLICKEY, publicKey.getEncoded())); } else { //此处产生的privatekey 的格式是 PKCS#8 的格式 pemWriter.writeObject(new PemObject(PEM_PRIVATEKEY, privateKey.getEncoded())); } pemWriter.flush(); } catch (IOException e) { e.printStackTrace(); } return stringWriter.toString(); } public static byte[] sign(String data, byte[] privateKey) throws Exception { PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey2 = keyFactory.generatePrivate(pkcs8EncodedKeySpec); Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initSign(privateKey2); signature.update(data.getBytes()); return signature.sign(); } //后台测试签名的时候 要和前台保持一致,所以需要将结果转换 private static String bytes2String(byte[] bytes) { StringBuilder string = new StringBuilder(); for (byte b : bytes) { String hexString = Integer.toHexString(0x00FF & b); string.append(hexString.length() == 1 ? "0" + hexString : hexString); } return string.toString(); } public static boolean verify(String data, byte[] publicKey, byte[] signatureResult) { try { X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey2 = keyFactory.generatePublic(x509EncodedKeySpec); Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initVerify(publicKey2); signature.update(data.getBytes()); return signature.verify(signatureResult); } catch (Exception e) { e.printStackTrace(); } return false; } //前台的签名结果是将byte 中的一些 负数转换成了正数, //但是后台验证的方法需要的又必须是转换之前的 public static byte[] hexStringToByteArray(String data) { int k = 0; byte[] results = new byte[data.length() / 2]; for (int i = 0; i + 1 < data.length(); i += 2, k++) { results[k] = (byte) (Character.digit(data.charAt(i), 16) << 4); results[k] += (byte) (Character.digit(data.charAt(i + 1), 16)); } return results; } public static void main(String[] args) { String str = "coder"; KeyPair k = generateRSAKeyPair(1024); String publicKey = convertToPemKey((RSAPublicKey) k.getPublic(), null); String privateKey = convertToPemKey(null, (RSAPrivateKey) k.getPrivate()); System.out.println("publicKey__\n" + publicKey); System.out.println("privateKey_\n" + privateKey); try { byte[] signautreResult = sign(str, k.getPrivate().getEncoded()); String signatureStr = bytes2String(signautreResult); byte[] signatureResult2 = hexStringToByteArray(signatureStr); boolean b = verify(str, k.getPublic().getEncoded(), signatureResult2); System.out.print("iii " + b); } catch (Exception e) { e.printStackTrace(); } } }
Javascript 签名用到的lib 是 jsrsasign 包含:
- Signature - RSA/RSAPSS/ECDSA/DSA digital signtature class wrapper of Java JCE style
- MessageDigest - cryptographic hash calculation class wrapper of Java JCE style
- MAC - message authentication code hash calculation class wrapper of Java JCE style
- ASN.1 encoder/generator
- ASN.1 structure for X.509 ceritificate, CRL and CSR(PKCS#10) generation
- ASN.1 structure for CMS SignedData generation
- ASN.1 structure for RFC 3161 TimeStamp generation
- ASN.1 structure for RFC 5126 CAdES Long Term Signature generation
- simple ASN.1 data parser
- simple X.509 certificate parser/reader
- KEYUTIL - loading RSA/EC/DSA private/public key from PEM formatted PKCS#1/5/8 and X.509 certificate
- JSON Web Siguature(JWS), JSON Web Token(JWT) and JSON Web Key(JWK)
更多的详细 github 地址:https://kjur.github.io/jsrsasign/
在官网给定的签名例子代码如下:
function doSign() { var rsa = new RSAKey(); rsa.readPrivateKeyFromPEMString(document.form1.prvkey1.value); var hashAlg = document.form1.hashalg.value; var hSig = rsa.signString(document.form1.msgsigned.value, hashAlg); document.form1.siggenerated.value = linebrk(hSig, 64); }
这里我们需要改动一下:
rsa.readPrivateKeyFromPEMString(document.form1.prvkey1.value); 官方api 中对这个方法有这样的说明:
readPrivateKeyFromPEMString(keyPEM)
read PKCS#1 private key from a string
这个方法传入的privatekey 是需要 PKCS#1 格式的,但是后台 产生出来的private key 是PKCS#8的格式的,这里就不能用这个方法,签名会通不过。
查看jsrsasign 的 api
KEYUTIL - loading RSA/EC/DSA private/public key from PEM formatted PKCS#1/5/8 and X.509 certificate
因此 js 生成RSAkey 对象
rsa=KEYUTIL.getKey(document.form1.prvkey1.value);
这个方法支持PKCS#8 pem 格式的privatekey 可以通过签名。