服务端生成公钥与私钥,保存。
客户端在请求到登录页面后,随机生成一字符串。
后此随机字符串作为密钥加密密码,再用从服务端获取到的公钥加密生成的随机字符串
将此两段密文传入服务端,服务端用私钥解出随机字符串,再用此私钥解出加密的密文。这其中有一个关键是解决服务端的公钥,传入客户端,客户端用此公钥加密字符串后,后又能在服务端用私钥解出。
步骤:
服务端的RSA Java实现:
/***
*/
package com.sunsoft.struts.util
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.math.BigInteger
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.NoSuchAlgorithmException
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.InvalidKeySpecException
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.RSAPublicKeySpec
import javax.crypto.Cipher
/**
* RSA 工具类。提供加密,解密,生成密钥对等方法。
* 需要到
下载bcprov-jdk14-123.jar。
*
*/
public class RSAUtil {
/**
* * 生成密钥对 *
*
* @return KeyPair *
* @throws EncryptException
*/
public static KeyPair generateKeyPair() throws Exception {
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
new org.bouncycastle.jce.provider.BouncyCastleProvider())
final int KEY_SIZE = 1024// 没什么好说的了,这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会低
keyPairGen.initialize(KEY_SIZE, new SecureRandom())
KeyPair keyPair = keyPairGen.generateKeyPair()
saveKeyPair(keyPair)
return keyPair
} catch (Exception e) {
throw new Exception(e.getMessage())
}
}
public static KeyPair getKeyPair()throws Exception{
FileInputStream fis = new FileInputStream("C:/RSAKey.txt")
ObjectInputStream oos = new ObjectInputStream(fis)
KeyPair kp= (KeyPair) oos.readObject()
oos.close()
fis.close()
return kp
}
public static void saveKeyPair(KeyPair kp)throws Exception{
FileOutputStream fos = new FileOutputStream("C:/RSAKey.txt")
ObjectOutputStream oos = new ObjectOutputStream(fos)
//生成密钥
oos.writeObject(kp)
oos.close()
fos.close()
}
/**
* * 生成公钥 *
*
* @param modulus *
* @param publicExponent *
* @return RSAPublicKey *
* @throws Exception
*/
public static RSAPublicKey generateRSAPublicKey(byte[] modulus,
byte[] publicExponent) throws Exception {
KeyFactory keyFac = null
try {
keyFac = KeyFactory.getInstance("RSA",
new org.bouncycastle.jce.provider.BouncyCastleProvider())
} catch (NoSuchAlgorithmException ex) {
throw new Exception(ex.getMessage())
}
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
modulus), new BigInteger(publicExponent))
try {
return (RSAPublicKey) keyFac.generatePublic(pubKeySpec)
} catch (InvalidKeySpecException ex) {
throw new Exception(ex.getMessage())
}
}
/**
* * 生成私钥 *
*
* @param modulus *
* @param privateExponent *
* @return RSAPrivateKey *
* @throws Exception
*/
public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus,
byte[] privateExponent) throws Exception {
KeyFactory keyFac = null
try {
keyFac = KeyFactory.getInstance("RSA",
new org.bouncycastle.jce.provider.BouncyCastleProvider())
} catch (NoSuchAlgorithmException ex) {
throw new Exception(ex.getMessage())
}
RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(
modulus), new BigInteger(privateExponent))
try {
return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec)
} catch (InvalidKeySpecException ex) {
throw new Exception(ex.getMessage())
}
}
/**
* * 加密 *
*
* @param key
* 加密的密钥 *
* @param data
* 待加密的明文数据 *
* @return 加密后的数据 *
* @throws Exception
*/
public static byte[] encrypt(PublicKey pk, byte[] data) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA",
new org.bouncycastle.jce.provider.BouncyCastleProvider())
cipher.init(Cipher.ENCRYPT_MODE, pk)
int blockSize = cipher.getBlockSize()// 获得加密块大小,如:加密前数据为128个byte,而key_size=1024
// 加密块大小为127
// byte,加密后为128个byte因此共有2个加密块,第一个127
// byte第二个为1个byte
int outputSize = cipher.getOutputSize(data.length)// 获得加密块加密后块大小
int leavedSize = data.length % blockSize
int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
: data.length / blockSize
byte[] raw = new byte[outputSize * blocksSize]
int i = 0
while (data.length - i * blockSize > 0) {
if (data.length - i * blockSize > blockSize)
cipher.doFinal(data, i * blockSize, blockSize, raw, i
* outputSize)
else
cipher.doFinal(data, i * blockSize, data.length - i
* blockSize, raw, i * outputSize)
// 这里面doUpdate方法不可用,查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到
// ByteArrayOutputStream中,而最后doFinal的时候才将所有的byte[]进行加密,可是到了此时加密块大小很可能已经超出了
// OutputSize所以只好用dofinal方法。
i++
}
return raw
} catch (Exception e) {
throw new Exception(e.getMessage())
}
}
/**
* * 解密 *
*
* @param key
* 解密的密钥 *
* @param raw
* 已经加密的数据 *
* @return 解密后的明文 *
* @throws Exception
*/
public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception {
try {
Cipher cipher = Cipher.getInstance("RSA",
new org.bouncycastle.jce.provider.BouncyCastleProvider())
cipher.init(cipher.DECRYPT_MODE, pk)
int blockSize = cipher.getBlockSize()
ByteArrayOutputStream bout = new ByteArrayOutputStream(64)
int j = 0
while (raw.length - j * blockSize > 0) {
bout.write(cipher.doFinal(raw, j * blockSize, blockSize))
j++
}
return bout.toByteArray()
} catch (Exception e) {
throw new Exception(e.getMessage())
}
}
/**
* * *
*
* @param args *
* @throws Exception
*/
public static void main(String[] args) throws Exception {
RSAPublicKey rsap = (RSAPublicKey) RSAUtil.generateKeyPair().getPublic()
String test = "hello world"
byte[] en_test = encrypt(getKeyPair().getPublic(),test.getBytes())
byte[] de_test = decrypt(getKeyPair().getPrivate(),en_test)
System.out.println(new String(de_test))
}
}
测试页面IndexAction.java:
/** Generated by MyEclipse Struts
* Template path: templates/java/JavaClass.vtl
*/
package com.sunsoft.struts.action
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import org.apache.struts.action.Action
import org.apache.struts.action.ActionForm
import org.apache.struts.action.ActionForward
import org.apache.struts.action.ActionMapping
import com.sunsoft.struts.util.RSAUtil
/**
* MyEclipse Struts
* Creation date: 06-28-2008
*
* XDoclet definition:
* @struts.action validate="true"
*/
public class IndexAction extends Action {
/*
* Generated Methods
*/
/**
* Method execute
* @param mapping
* @param form
* @param request
* @param response
* @return ActionForward
*/
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws Exception {
RSAPublicKey rsap = (RSAPublicKey) RSAUtil.getKeyPair().getPublic()
String module = rsap.getModulus().toString(16)
String empoent = rsap.getPublicExponent().toString(16)
System.out.println("module")
System.out.println(module)
System.out.println("empoent")
System.out.println(empoent)
request.setAttribute("m", module)
request.setAttribute("e", empoent)
return mapping.findForward("login")
}
}
通过此action进入登录页面,并传入公钥的 Modulus 与PublicExponent的hex编码形式。
npm 安装 jsrsasign
封装方法
前端H5的使用
定义方法
使用方法
使用自己的私钥签名
使用后台交换的平台公钥验证签名
签名的工作最好在后端完成,前台不暴露公钥私钥。
如果你的填充模式不是PKCS5Padding肯定就解密不了了CryptoJS.aes.encrypt(srcs, key, { iv: iv,mode:CryptoJS.mode.cbc.padding:CryptoJS.pad.NoPadding})
CryptoJS可以用的填充模式:
Pkcs7 (the default)
Iso97971
AnsiX923
Iso10126
ZeroPadding