Подпись ключа подтверждается в Python, не будет ли в Java?

Я создал пару ключей в python, используя pycrypto

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, но я начал сталкиваться с проблемами. А именно, моя проверка всегда терпит неудачу...

Я начинаю с создания объекта PublicKey из экспортируемого PEM I...

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 представляет их как unsigned, но они одинаковы двоичное представление). Но, похоже, всегда не удается проверить мою подпись... что я использую для этого:

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

InputStream hashStream = new ********************(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")));

К сожалению, это только печатает false.

Единственное отличие, которое я действительно вижу, это то, что когда я передаю его для проверки на Java, он запрашивает массив longs, тогда как в python ему нужна последовательность байтов. Мое лучшее предположение заключалось в том, чтобы взять строковое представление этого длинного и преобразовать его в кучу байтов, но это не удалось. Все мои другие попытки также потерпели неудачу (посмотрите на представление байта основного целого числа, посмотрите на представление байта массива и т.д.). Я чувствую, что упускаю что-то ДЕЙСТВИТЕЛЬНО просто, но для жизни я не могу понять, что это такое...

Для примера того, как выглядит подпись, в python мне дают:

[688304594898632574115230115201042030356261470845487427579402264460794863484312 120410963342371307037749493750151877472804877900061168981924606440672704577286260 395240971170923041153667805814235978868869872792318501209376911650132169706471509 89646220735762034864029622135210042186666476516651349805320771941650]

1 ответ

Вы обрабатываете подпись как строку 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 и более высокий размер ключа. Кроме того, использование MD5 нарушено для приложений подписи. Вместо этого используйте SHA-256 или SHA-512.

licensed under cc by-sa 3.0 with attribution.