1

我使用 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]

4

1 回答 1

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)));
    }
}

[编辑2]

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

为了更安全,请尝试使用 PSS 样式签名和更大的密钥大小。此外,签名应用程序无法使用 MD5。请改用 SHA-256 或 SHA-512。

于 2013-10-26T01:52:36.043 回答