前几天做了个通信软件,自建服务器,由于服务器用的golang(ps:个人非常喜欢golang,语法的简洁,并发的优美,还有一点就是依赖库一起编译,适用起来少了许多麻烦,
现在这么大的硬盘存储空间,根本不却那点动态库的位置,最主要的一点是opensource)。
好了,说正题,rsa加密算法是现在通用的非对成加密,细节自行bing(某谷搜索引擎也用不了)。但是各种语言的实现细节还是有点区别的,唯一不变的是它们都是
做的大数运算(算法自行bing)。
由于需要客户端加密,签名,服务器也需要加密和签名。所以做起来就有点麻烦了。干脆所有细节以服务器为准,服务器用什么算法,客户端就用什么算法。
这里golang适用了pkcs1格式的密钥,所以android中就要适用pkcs1格式。后来由于rsa签名及验证的时候一直不对,所以参照了golang的签名验证算法写了个android
版本的。所以这里rsa密钥的存储就只能用最原始的方法了,就是直接存储rsa中的三个大数:modulus、exponents和public exponents。做rsa加密解密及签名验证
都是用的这三个数。
当服务器中生成rsa公钥及私钥的时候就可以获得这三个数并保存起来。其中加密用到了modulus和public exponents,java版本的解密用到了modulus和exponents。
而golang版本的解密三个数都需要。这是因为golang中创建私钥的时候需要验证(自行查看golang源码:crypto/rsa中)。
不废话了,golang中rsa的使用自行搜索。这里就贴出android中的加密解密及签名验证代码:
package hack.android3.crypto;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
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 java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import hack.android3.utility.Error;
import hack.android3.utility.Result;
public class Crypto {
static int gcRsaKeyLength = 512;
static int gcAesKeyLength = 128;
private static SimpleDateFormat dateFormat = new SimpleDateFormat("M/d H:m:s");
public static String getTime(long time) {
Date date = new Date(time * 1000);
return dateFormat.format(date);
}
// genUUID gen a uuid string.
public static String genUUID() {
return UUID.randomUUID().toString();
}
// getString get a string from byte array.
public static String getString(byte[] text) {
StringBuilder stringBuilder = new StringBuilder();
for (byte i : text) {
stringBuilder.append((char) i);
}
return stringBuilder.toString();
}
// genAesKey generate a random byte array.
public static byte[] genAesKey() {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(gcAesKeyLength);
return keyGenerator.generateKey().getEncoded();
} catch (NoSuchAlgorithmException e) {
return null;
}
}
// genRsaKey create a rsa key and return the modulus,public exponent and private exponent.
public static Result<String[]> genRsaKey() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(gcRsaKeyLength, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Result<String[]> result = new Result<>();
result.Result = new String[3];
result.Result[0] = publicKey.getModulus().toString();
result.Result[1] = publicKey.getPublicExponent().toString();
result.Result[2] = privateKey.getPrivateExponent().toString();
return result;
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
}
}
// rsaEncrypt encrypt data use rsa algorithm.
// 这里需要传入modulus和publicexponents。
public static Result<byte[]> rsaEncrypt(String rsaMod, String rsaPub, byte[] text) {
Result<PublicKey> publicKey = getPublicKey(rsaMod, rsaPub);
if (publicKey.Error != null) {
return new Result<>(publicKey.Error);
}
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey.Result);
return new Result<>(cipher.doFinal(text));
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
} catch (NoSuchPaddingException e) {
return new Result<>(e);
} catch (InvalidKeyException e) {
return new Result<>(e);
} catch (IllegalBlockSizeException e) {
return new Result<>(e);
} catch (BadPaddingException e) {
return new Result<>(e);
}
}
// rsaDecrypt decrypt data use rsa algorithm.
// 这里和golang不同,只需要传入modulus和exponents就可以。
public static Result<byte[]> rsaDecrypt(String rsaMod, String rsaPri, byte[] text) {
Result<PrivateKey> privateKey = getPrivateKey(rsaMod, rsaPri);
if (privateKey.Error != null) {
return new Result<>(privateKey.Error);
}
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.DECRYPT_MODE, privateKey.Result);
return new Result<>(cipher.doFinal(text));
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
} catch (NoSuchPaddingException e) {
return new Result<>(e);
} catch (InvalidKeyException e) {
return new Result<>(e);
} catch (BadPaddingException e) {
return new Result<>(e);
} catch (IllegalBlockSizeException e) {
return new Result<>(e);
}
}
// rsaSign sign text and return signature.
// 签名的时候用的是私钥,所以需要modulus和exponents。
public static Result<byte[]> rsaSign(String rsaMod, String rsaPri, byte[] text) {
Result<PrivateKey> privateKey = getPrivateKey(rsaMod, rsaPri);
if (privateKey.Error != null) {
return new Result<>(privateKey.Error);
}
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.update(text);
return sign((RSAPrivateKey) privateKey.Result, sha1.digest());
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
}
}
// rsaVerify verify signature.
// 验证的时候用的是公钥,需要modulus和public expoentns。
public static Result<Boolean> rsaVerify(String rsaMod, String rsaPub, byte[] text, byte[] signature) {
Result<PublicKey> publicKey = getPublicKey(rsaMod, rsaPub);
if (publicKey.Error != null) {
return new Result<>(publicKey.Error);
}
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.update(text);
return verify((RSAPublicKey) publicKey.Result, sha1.digest(), signature);
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
}
}
// md5Encrypt encrypt data use md5 algorithm and hex encode result.
public static Result<String> md5Encrypt(String data) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(data.getBytes());
byte[] encryptedData = md5.digest();
// get hex string
StringBuilder stringBuilder = new StringBuilder(32);
for (byte b : encryptedData) {
stringBuilder.append(Integer.toHexString(0xFF & b));
}
return new Result<>(stringBuilder.toString());
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
}
}
public static Result<byte[]> aesEncrypt(byte[] key, byte[] text) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(key));
return new Result<>(cipher.doFinal(text));
} catch (IllegalBlockSizeException e) {
return new Result<>(Error.New(e.getMessage()));
} catch (InvalidKeyException e) {
return new Result<>(Error.New(e.getMessage()));
} catch (BadPaddingException e) {
return new Result<>(Error.New(e.getMessage()));
} catch (NoSuchAlgorithmException e) {
return new Result<>(Error.New(e.getMessage()));
} catch (NoSuchPaddingException e) {
return new Result<>(Error.New(e.getMessage()));
} catch (InvalidAlgorithmParameterException e) {
return new Result<>(Error.New(e.getMessage()));
}
}
public static Result<byte[]> aesDecrypt(byte[] key, byte[] text) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(key));
return new Result<>(cipher.doFinal(text));
} catch (IllegalBlockSizeException e) {
return new Result<>(e);
} catch (InvalidKeyException e) {
return new Result<>(e);
} catch (BadPaddingException e) {
return new Result<>(e);
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
} catch (NoSuchPaddingException e) {
return new Result<>(e);
} catch (InvalidAlgorithmParameterException e) {
return new Result<>(e);
}
}
// 这里适用modulus和public expoennts生成公钥。
private static Result<PublicKey> getPublicKey(String rsaMod, String rsaPub) {
try {
BigInteger modulusInt = new BigInteger(rsaMod, 10);
BigInteger publicExponentInt = new BigInteger(rsaPub, 10);
// create public key
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulusInt, publicExponentInt);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return new Result<>(keyFactory.generatePublic(publicKeySpec));
} catch (NullPointerException e) {
return new Result<>(e);
} catch (NumberFormatException e) {
return new Result<>(e);
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
} catch (InvalidKeySpecException e) {
return new Result<>(e);
}
}
// 这里使用modulus和exponents生成私钥。
private static Result<PrivateKey> getPrivateKey(String rsaMod, String rsaPri) {
try {
BigInteger modulusInt = new BigInteger(rsaMod, 10);
BigInteger privateExponentInt = new BigInteger(rsaPri, 10);
// create private key
RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(modulusInt, privateExponentInt);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return new Result<>(keyFactory.generatePrivate(privateKeySpec));
} catch (NullPointerException e) {
return new Result<>(e);
} catch (NumberFormatException e) {
return new Result<>(e);
} catch (NoSuchAlgorithmException e) {
return new Result<>(e);
} catch (InvalidKeySpecException e) {
return new Result<>(e);
}
}
// rsa pkcs1 sign and verify.
// translate by go language crypto/rsa,pkcs1.
// 这是参照golang源码写的rsa签名方法。
private static Result<byte[]> sign(RSAPrivateKey pri, byte[] hashed) {
int hashLen = hashed.length;
int tLen = hashLen;
int k = (pri.getModulus().bitLength() + 7) / 8;
if (k < tLen + 11) {
return new Result<>(Error.New("the k < tLen"));
}
byte[] em = new byte[k];
em[1] = (byte) 1;
for (int i = 2; i < k - tLen - 1; i++) {
em[i] = (byte) 0xff;
}
System.arraycopy(hashed, 0, em, k - hashLen, hashed.length);
BigInteger m = new BigInteger(em);
if (m.compareTo(pri.getModulus()) > 0) {
return new Result<>(Error.New("the m > pri.getModulus()"));
}
BigInteger c = m.modPow(pri.getPrivateExponent(), pri.getModulus());
copyWithLeftPad(em, c.toByteArray());
return new Result<>(em);
}
// 这是参照golang源码写的rsa验证方法。
private static Result<Boolean> verify(RSAPublicKey pub, byte[] hashed, byte[] sig) {
int hashLen = hashed.length;
int tLen = hashLen;
int k = (pub.getModulus().bitLength() + 7) / 8;
if (k < tLen + 11) {
return new Result<>(Error.New("the k < tLen"));
}
// add sign bytes to sig head.
byte[] sig2 = new byte[sig.length + 1];
sig2[0] = (byte) 0;
System.arraycopy(sig, 0, sig2, 1, sig.length);
BigInteger c = new BigInteger(sig2);
BigInteger m = c.modPow(pub.getPublicExponent(), pub.getModulus());
byte[] em = leftPad(m.toByteArray(), k);
int ok = constantTimeByteEq(em[0], (byte) 0);
ok &= constantTimeByteEq(em[1], (byte) 1);
ok &= constantTimeCompare(em, k - hashLen, k, hashed);
ok &= constantTimeByteEq(em[k - tLen - 1], (byte) 0);
for (int i = 2; i < k - tLen - 1; i++) {
ok &= constantTimeByteEq(em[i], (byte) 0xff);
}
if (ok != -1) {
return new Result<>(Error.New("ok is not -1"));
}
return new Result<>(true);
}
// 这是参照golang源码写的方法。
private static byte[] leftPad(byte[] input, int size) {
int n = input.length;
if (n > size) {
n = size;
}
byte[] out = new byte[size];
if (input.length > out.length) {
System.arraycopy(input, 1, out, out.length - n, input.length - 1);
} else {
System.arraycopy(input, 0, out, out.length - n, input.length);
}
return out;
}
// 这是参照golang源码写的。
private static void copyWithLeftPad(byte[] dest, byte[] src) {
// java's biginteger to bytearray is include sig,
// so should delete first byte.
int numPaddingBytes = dest.length - src.length;
for (int i = 0; i < numPaddingBytes; i++) {
dest[i] = 0;
}
if (src.length > dest.length) {
System.arraycopy(src, 1, dest, numPaddingBytes + 1, src.length - 1);
} else {
System.arraycopy(src, 0, dest, numPaddingBytes, src.length);
}
}
// 这是参照golang源码写的。
// 其中有趣的是:这个方法是用来比较两个字节是否相等的,当然用==也可以,但是生成的汇编代码不一样(这是针对编译型语言,
// 这里直接照搬过来了)。
// x = 01010011
// y = 00010011
// x ^ y = 01000000
// ~(x^y) = 10111111 => z
// z >> 4 = 00001011
// z & (z >> 4) = 00001011
// z = 00001011
// z >> 2 = 00000010
// z & (z >> 4) = 00000010
// z = 00000010
// z >> 1 = 00000001
// z & (z >> 1) = 00000000
private static int constantTimeByteEq(byte x, byte y) {
int z = ~(x ^ y);
z &= z >> 4;
z &= z >> 2;
z &= z >> 1;
return z;
}
// 这是参照golang源码写的。
private static int constantTimeCompare(byte[] x, int start, int end, byte[] y) {
if (end - start != y.length) {
return 0;
}
byte v = 0;
for (int i = 0; i < y.length; i++) {
v |= x[start + i] ^ y[i];
}
return constantTimeByteEq(v, (byte) 0);
}
}