Ключевая подпись проверяется в 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...

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

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

[688304594898632574115230115201042030356261470845487427579402264460794863484312‌​120410963342371307037749493750151877472804877900061168981924606440672704577286260‌​395240971170923041153667805814235978868869872792318501209376911650132169706471509‌​89646220735762034864029622135210042186666476516651349805320771941650]


person DivineWolfwood    schedule 26.10.2013    source источник


Ответы (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.

person Maarten Bodewes    schedule 26.10.2013
comment
Как тогда я должен относиться к подписи? Он передается мне как сообщение, поэтому я получаю его как строку, но я могу преобразовать его соответствующим образом. Проблемы, которые у меня были, заключались в том, что любой разумный кастинг также терпит неудачу, и я просто не знаю, что делать. - person DivineWolfwood; 28.10.2013
comment
Покажите нам, как выглядит подпись, я ничего не могу сказать, если не знаю кодировку строки подписи. Если он просто передается как кодировка без непечатаемых символов, вы теряете данные при передаче, и вы мало что можете сделать. - person Maarten Bodewes; 28.10.2013
comment
В питоне он в основном передается как массив одного большого целого числа и проверяет его соответствующим образом. Here's an example: [68830459489863257411523011520104203035626147084548742757940226446079486348431212041096334237130703774949375015187747280487790006116898192460644067270457728626039524097117092304115366780581423597886886987279231850120937691165013216970647150989646220735762034864029622135210042186666476516651349805320771941650] - person DivineWolfwood; 28.10.2013
comment
Ах, хм, фокус в том, чтобы создать из этого двоичный код. Вам понадобится двоичное представление с обратным порядком байтов, без знака, заполненное слева, того же размера, что и ключ. Вы можете это сделать или вам нужен какой-то код? PS отредактируйте это в вопросе! - person Maarten Bodewes; 28.10.2013
comment
Спасибо, я отредактировал вопрос. Я действительно мог бы использовать здесь какой-то пример кода, просто потому, что отладка это такой кошмар, потому что все, что я получаю, это то, что это сработало или не сработало. - person DivineWolfwood; 28.10.2013
comment
Обновленный ответ, используйте byte[] перед кодированием в шестнадцатеричный код, очевидно :) - person Maarten Bodewes; 28.10.2013
comment
давайте продолжим это обсуждение в чате - person DivineWolfwood; 28.10.2013