RSA Java 投诉签名和验证
RSA Java complaint signing and verfiying
我正在尝试编写自己的 RSA 库来签名、验证、加密和解密。下面是代码,它使用 BigInteger 并支持根据 PKCS#1 规范从 BigInteger 到字节数组(八位字节字符串)的转换
class RSA {
public static RSAKeyPair generateKeyPair(int size) {
Random rnd = new SecureRandom();
BigInteger p = new BigInteger(size / 2, 100, rnd);
BigInteger q = new BigInteger(size / 2, 100, rnd);
BigInteger n = p.multiply(q);
BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
BigInteger e;
do {
e = new BigInteger(phi.bitLength(), rnd);
} while (e.compareTo(BigInteger.ONE) <= 0 || e.compareTo(phi) >= 0 || !e.gcd(phi).equals(BigInteger.ONE));
BigInteger d = e.modInverse(phi);
return new RSAKeyPair(new RSAPublicKey(n, e), new RSAPrivateKey(n, d), n);
}
public static BigInteger encrypt(BigInteger m, RSAPublicKey key) {
return m.modPow(key.getPublicExponent(), key.getModulus());
}
public static BigInteger decrypt(BigInteger c, RSAPrivateKey key) {
return c.modPow(key.getPrivateExponent(), key.getModulus());
}
public static BigInteger sign(BigInteger m, RSAPrivateKey key) {
return m.modPow(key.getPrivateExponent(), key.getModulus());
}
public static boolean verify(BigInteger m, BigInteger s, RSAPublicKey key) {
return s.modPow(key.getPublicExponent(), key.getModulus()).equals(m);
}
public static BigInteger OS2IP(byte[]X){
BigInteger out = new BigInteger("0");
BigInteger twofiftysix = new BigInteger("256");
for(int i = 1; i <= X.length; i++){
out = out.add((BigInteger.valueOf(0xFF & X[i - 1])).multiply(twofiftysix.pow(X.length-i)));
}
//x = x(xLen–1)^256xLen–1 + x(xLen–2)^256xLen–2 + … + x(1)^256 + x0
return out;
}
public static byte[] I2OSP(BigInteger X, int XLen){
BigInteger twofiftysix = new BigInteger("256");
byte[] out = new byte[XLen];
BigInteger[] cur;
if(X.compareTo(twofiftysix.pow(XLen)) >= 0){
return new String("integer too large").getBytes();
}
for(int i = 1; i <= XLen; i++){
cur = X.divideAndRemainder(twofiftysix.pow(XLen-i));
//X = cur[1];
out[i - 1] = cur[0].byteValue();
}
//basically the inverse of the above
//Cur is an array of two bigints, with cur[0]=X/256^(XLen-i) and cur[1]=X/256^[XLen-i]
return out;
}
}
class RSAKeyPair {
private RSAPublicKey pub;
private RSAPrivateKey priv;
private BigInteger n;
public RSAKeyPair(RSAPublicKey pub, RSAPrivateKey priv, BigInteger n) {
this.pub = pub;
this.priv = priv;
this.n = n;
}
public RSAPublicKey getPublicKey() {
return pub;
}
public RSAPrivateKey getPrivateKey() {
return priv;
}
public BigInteger getModulus() {
return n;
}
}
class RSAPublicKey {
private BigInteger n;
private BigInteger e;
public RSAPublicKey(BigInteger n, BigInteger e) {
this.n = n;
this.e = e;
}
public BigInteger getModulus() {
return n;
}
public BigInteger getPublicExponent() {
return e;
}
}
class RSAPrivateKey {
private BigInteger n;
private BigInteger d;
public RSAPrivateKey(BigInteger n, BigInteger d) {
this.n = n;
this.d = d;
}
public BigInteger getModulus() {
return n;
}
public BigInteger getPrivateExponent() {
return d;
}
}
当我使用 class 签名并验证时,一切正常,但是如果我使用 Java api 签名并使用此 RSA class 进行验证,则验证失败.
public static void main(String[] args) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
byte[] data = "fooo".getBytes();
System.out.println("data is:" + Arrays.toString(data));
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(1024);
KeyPair pair = gen.generateKeyPair();
Signature sig = Signature.getInstance("NONEwithRSA");
sig.initSign(pair.getPrivate());
sig.update(data);
byte[] sign = sig.sign();
System.out.println("signature is: " +Arrays.toString(sign) + "\n" + "length" + sign.length);
java.security.interfaces.RSAPublicKey pub = (java.security.interfaces.RSAPublicKey) pair.getPublic();
java.security.interfaces.RSAPrivateKey pri = (java.security.interfaces.RSAPrivateKey) pair.getPrivate();
BigInteger m = RSA.OS2IP(data);
BigInteger c = RSA.OS2IP(sign);
RSAPublicKey key = new RSAPublicKey(pub.getModulus(), pub.getPublicExponent()); // not the java.security.interfaces.RSAPublicKey rather the RSAPublicKey from class see above
RSAPrivateKey pkey = new RSAPrivateKey(pri.getModulus(), pri.getPrivateExponent());
BigInteger signing = RSA.sign(m, pkey);
boolean verify = RSA.verify(m, signing, key);
boolean verify_java = RSA.verify(m, c, key);
System.out.println("signing and verfiying usinf RSA class:" +verify);
System.out.println("signing using java api and verify using RSA class:"+verify_java);
}
知道为什么在使用 java api 签名时验证失败吗?
NONEwithRSA
最初使用 PKCS#1 v1.5 填充(更准确地说是 RSASSA-PKCS1-v1_5)填充数据,这在您的自定义实现中不会发生。所以,在RSA.verify(m, c, key)
中,m
没有被填充,而c
对应的是填充数据的签名
因此,为了通过
与c
成功验证
boolean verify_java = RSA.verify(m, c, key);
m
必须替换为
BigInteger mPadded = RSA.OS2IP(dataPadded)
其中dataPadded
根据keysize是128字节的byte[]
,内容如下(见RFC8017):
00 01 ff ... ff 00 66 6f 6f 6f
您可以导出此值,例如与:
byte[] dataPadded = pad(data, 1024/8);
...
private static byte[] pad(byte[] data, int length) {
byte[] dataPadded = new byte[length];
for (int i = 0; i < dataPadded.length; i++)
dataPadded[i] = (byte)255;
System.arraycopy(data, 0, dataPadded, dataPadded.length - data.length, data.length);
dataPadded[0] = 0;
dataPadded[1] = 1;
dataPadded[dataPadded.length - data.length - 1] = 0;
return dataPadded;
}
我正在尝试编写自己的 RSA 库来签名、验证、加密和解密。下面是代码,它使用 BigInteger 并支持根据 PKCS#1 规范从 BigInteger 到字节数组(八位字节字符串)的转换
class RSA {
public static RSAKeyPair generateKeyPair(int size) {
Random rnd = new SecureRandom();
BigInteger p = new BigInteger(size / 2, 100, rnd);
BigInteger q = new BigInteger(size / 2, 100, rnd);
BigInteger n = p.multiply(q);
BigInteger phi = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));
BigInteger e;
do {
e = new BigInteger(phi.bitLength(), rnd);
} while (e.compareTo(BigInteger.ONE) <= 0 || e.compareTo(phi) >= 0 || !e.gcd(phi).equals(BigInteger.ONE));
BigInteger d = e.modInverse(phi);
return new RSAKeyPair(new RSAPublicKey(n, e), new RSAPrivateKey(n, d), n);
}
public static BigInteger encrypt(BigInteger m, RSAPublicKey key) {
return m.modPow(key.getPublicExponent(), key.getModulus());
}
public static BigInteger decrypt(BigInteger c, RSAPrivateKey key) {
return c.modPow(key.getPrivateExponent(), key.getModulus());
}
public static BigInteger sign(BigInteger m, RSAPrivateKey key) {
return m.modPow(key.getPrivateExponent(), key.getModulus());
}
public static boolean verify(BigInteger m, BigInteger s, RSAPublicKey key) {
return s.modPow(key.getPublicExponent(), key.getModulus()).equals(m);
}
public static BigInteger OS2IP(byte[]X){
BigInteger out = new BigInteger("0");
BigInteger twofiftysix = new BigInteger("256");
for(int i = 1; i <= X.length; i++){
out = out.add((BigInteger.valueOf(0xFF & X[i - 1])).multiply(twofiftysix.pow(X.length-i)));
}
//x = x(xLen–1)^256xLen–1 + x(xLen–2)^256xLen–2 + … + x(1)^256 + x0
return out;
}
public static byte[] I2OSP(BigInteger X, int XLen){
BigInteger twofiftysix = new BigInteger("256");
byte[] out = new byte[XLen];
BigInteger[] cur;
if(X.compareTo(twofiftysix.pow(XLen)) >= 0){
return new String("integer too large").getBytes();
}
for(int i = 1; i <= XLen; i++){
cur = X.divideAndRemainder(twofiftysix.pow(XLen-i));
//X = cur[1];
out[i - 1] = cur[0].byteValue();
}
//basically the inverse of the above
//Cur is an array of two bigints, with cur[0]=X/256^(XLen-i) and cur[1]=X/256^[XLen-i]
return out;
}
}
class RSAKeyPair {
private RSAPublicKey pub;
private RSAPrivateKey priv;
private BigInteger n;
public RSAKeyPair(RSAPublicKey pub, RSAPrivateKey priv, BigInteger n) {
this.pub = pub;
this.priv = priv;
this.n = n;
}
public RSAPublicKey getPublicKey() {
return pub;
}
public RSAPrivateKey getPrivateKey() {
return priv;
}
public BigInteger getModulus() {
return n;
}
}
class RSAPublicKey {
private BigInteger n;
private BigInteger e;
public RSAPublicKey(BigInteger n, BigInteger e) {
this.n = n;
this.e = e;
}
public BigInteger getModulus() {
return n;
}
public BigInteger getPublicExponent() {
return e;
}
}
class RSAPrivateKey {
private BigInteger n;
private BigInteger d;
public RSAPrivateKey(BigInteger n, BigInteger d) {
this.n = n;
this.d = d;
}
public BigInteger getModulus() {
return n;
}
public BigInteger getPrivateExponent() {
return d;
}
}
当我使用 class 签名并验证时,一切正常,但是如果我使用 Java api 签名并使用此 RSA class 进行验证,则验证失败.
public static void main(String[] args) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException {
byte[] data = "fooo".getBytes();
System.out.println("data is:" + Arrays.toString(data));
KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
gen.initialize(1024);
KeyPair pair = gen.generateKeyPair();
Signature sig = Signature.getInstance("NONEwithRSA");
sig.initSign(pair.getPrivate());
sig.update(data);
byte[] sign = sig.sign();
System.out.println("signature is: " +Arrays.toString(sign) + "\n" + "length" + sign.length);
java.security.interfaces.RSAPublicKey pub = (java.security.interfaces.RSAPublicKey) pair.getPublic();
java.security.interfaces.RSAPrivateKey pri = (java.security.interfaces.RSAPrivateKey) pair.getPrivate();
BigInteger m = RSA.OS2IP(data);
BigInteger c = RSA.OS2IP(sign);
RSAPublicKey key = new RSAPublicKey(pub.getModulus(), pub.getPublicExponent()); // not the java.security.interfaces.RSAPublicKey rather the RSAPublicKey from class see above
RSAPrivateKey pkey = new RSAPrivateKey(pri.getModulus(), pri.getPrivateExponent());
BigInteger signing = RSA.sign(m, pkey);
boolean verify = RSA.verify(m, signing, key);
boolean verify_java = RSA.verify(m, c, key);
System.out.println("signing and verfiying usinf RSA class:" +verify);
System.out.println("signing using java api and verify using RSA class:"+verify_java);
}
知道为什么在使用 java api 签名时验证失败吗?
NONEwithRSA
最初使用 PKCS#1 v1.5 填充(更准确地说是 RSASSA-PKCS1-v1_5)填充数据,这在您的自定义实现中不会发生。所以,在RSA.verify(m, c, key)
中,m
没有被填充,而c
对应的是填充数据的签名
因此,为了通过
与c
成功验证
boolean verify_java = RSA.verify(m, c, key);
m
必须替换为
BigInteger mPadded = RSA.OS2IP(dataPadded)
其中dataPadded
根据keysize是128字节的byte[]
,内容如下(见RFC8017):
00 01 ff ... ff 00 66 6f 6f 6f
您可以导出此值,例如与:
byte[] dataPadded = pad(data, 1024/8);
...
private static byte[] pad(byte[] data, int length) {
byte[] dataPadded = new byte[length];
for (int i = 0; i < dataPadded.length; i++)
dataPadded[i] = (byte)255;
System.arraycopy(data, 0, dataPadded, dataPadded.length - data.length, data.length);
dataPadded[0] = 0;
dataPadded[1] = 1;
dataPadded[dataPadded.length - data.length - 1] = 0;
return dataPadded;
}