密钥签名在Python中验证,不会在Java中?

时间:2013-10-26 01:20:03

标签: java python encryption rsa signing

我使用pycrypto

在python中生成了一个密钥对
key=RSA.generate(bit_size,os.urandom)

exportedPrivateKey = key.exportKey('PEM', None, pkcs=1).decode("utf-8")
exportedPublicKey = key.publickey().exportKey('PEM', None, pkcs=1).decode("utf-8")

我写了一个小实用程序,它接收消息的哈希并签署哈希...

hash = MD5.new(json_info.encode("utf-8")).digest()
privateKey = RSA.importKey(USER_TOKEN_PRIVATE_KEY)
signature = privateKey.sign(hash,'')

然后我写了一些使用公钥来验证它已经验证好的东西......我的令牌中的签名工作得很好..

hash = MD5.new(packet.encode("utf-8")).digest()
publicKey = RSA.importKey(tokenPublicKey)

if publicKey.verify(hash, signature):
    return json.loads(packet)
else:
    return None

现在,因为我需要在Java和python中使用它,我将类似的库移植到java,但我开始遇到问题。也就是说,我的验证总是会失败......

首先从我导出的PEM中创建PublicKey对象...

byte[] encoded = Base64.decodeBase64(USER_TOKEN_PUBLIC_KEY);

//decode the encoded RSA public key
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(keySpec);

我能够获得签名,它是完全相同的签名,值哈希到完全相同的值(嗯,类似; java表示字节为有符号整数,而python表示它们为无符号,但它们'重复相同的二进制表示)。但它似乎总是无法验证我的签名......这就是我用来做的事情:

byte[] hash = hasher.digest(packet.getBytes("UTF-8"));

InputStream hashStream = new ByteArrayInputStream(hash);

final Signature sign = Signature.getInstance("MD5withRSA");
sign.initVerify(pubKey);

byte[] buffer = new byte[256];
int length;
while ((length = hashStream.read (buffer)) != -1)
    sign.update (buffer, 0, length);

hashStream.close();

System.out.println(sign.verify(signature.getBytes("UTF-8")));

不幸的是,这只会打印错误。

我能真正看到的唯一区别是,当我传递它以在Java中验证时,它要求一个long数组,而在python中它需要一个字节序列。我最好的猜测是获取那个long的字符串表示并将其转换为一堆字节,但是失败了。我的所有其他尝试都失败了(查看底层大整数的字节表示,查看数组的字节表示等)。我觉得我错过了一些非常简单的东西,但对于我的生活,我无法弄清楚它是什么......

有关签名的示例,在python中,我给出了:

  

[688304594898632574115230115201042030356261470845487427579402264460794863484312 120410963342371307037749493750151877472804877900061168981924606440672704577286260 395240971170923041153667805814235978868869872792318501209376911650132169706471509 89646220735762034864029622135210042186666476516651349805320771941650]

1 个答案:

答案 0 :(得分:2)

您将签名作为Java字符串处理,使用该字符串的UTF-8编码作为签名值。由于签名可以是任何编码,包括不编码为可打印字符串的字节,这些编码可能不正确。

[编辑]

好的,所以整数看起来像1024位签名,表示为括号之间的数字。所以这段代码应该有所帮助:

 
import java.math.BigInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SignatureFromPython {
    private static final Pattern PAT = Pattern.compile("\\[(\\d+)\\]");

    private static byte[] i2osp(final BigInteger i, final int bitSize) {
        if (i == null || i.signum() == -1) {
            throw new IllegalArgumentException(
                    "input parameter should not be null or negative");
        }

        if (bitSize < Byte.SIZE) {
            throw new IllegalArgumentException(
                    "bitSize parameter should not be negative and a multiple of 8");
        }

        final int byteSize = (bitSize - 1) / Byte.SIZE + 1;
        final byte[] signedBigEndian = i.toByteArray();
        final int signedBigEndianLength = signedBigEndian.length;
        if (signedBigEndianLength == byteSize) {
            return signedBigEndian;
        }

        final byte[] leftPadded = new byte[byteSize];

        if (signedBigEndianLength == byteSize + 1) {
            System.arraycopy(signedBigEndian, 1, leftPadded, 0, byteSize);
        } else if (signedBigEndianLength < byteSize) {
            System.arraycopy(signedBigEndian, 0, leftPadded, byteSize
                    - signedBigEndianLength, signedBigEndianLength);
        } else {
            throw new IllegalArgumentException(
                    "Integer i is too large to fit into " + bitSize + " bits");
        }
        return leftPadded;
    }

    public static String toHex(final byte[] data) {
        final StringBuilder hex = new StringBuilder(data.length * 2);
        for (int i = 0; i < data.length; i++) {
            hex.append(String.format("%02X", data[i]));
        }
        return hex.toString();
    }

    public static void main(String[] args) {
        String sigString = "[68830459489863257411523011520104203035626147084548742757940226446079486348431212041096334237130703774949375015187747280487790006116898192460644067270457728626039524097117092304115366780581423597886886987279231850120937691165013216970647150989646220735762034864029622135210042186666476516651349805320771941650]";
        Matcher sigMatcher = PAT.matcher(sigString);
        if (!sigMatcher.matches()) {
            throw new IllegalArgumentException("Whatever");
        }
        BigInteger sigBI = new BigInteger(sigMatcher.group(1));
        // requires bouncy castle libraries
        System.out.println(toHex(i2osp(sigBI, 1024)));
    }
}

[EDIT2]

privateKey.sign(hash,'')使用“原始”RSA签名。需要使用PKCS115_SigScheme代替。

为了更安全,请尝试使用PSS样式签名和higher key size。此外,对于签名应用程序,MD5的使用已被破坏。改为使用SHA-256或SHA-512。