Подписание и проверка жалоб RSA Java

Я пытаюсь написать свою собственную библиотеку RSA, которая подписывает, проверяет, шифрует и расшифровывает. Ниже приведен код, который использует BigInteger и поддерживает преобразование из BigInteger в массив байтов (строка октетов) и наоборот в соответствии со спецификациями PKCS#1.

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

}

Когда я подписываю и проверяю, используя этот класс, все работает нормально, однако, если я подписываю с помощью API Java и проверяю, используя этот класс RSA, проверка завершается ошибкой.

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 для подписи?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
108
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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 — это byte[] из 128 байтов в соответствии с размером ключа, который имеет следующее содержание (см. 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;
}

Другие вопросы по теме

Как мне проверить подпись ED25519 длиной 128 символов?
Есть ли существенная разница между использованием встроенного генератора случайных чисел и использованием криптографического генератора случайных чисел?
Как расшифровать криптографию NodeJS на стороне клиента с помощью известного ключа шифрования?
Обратный инжиниринг алгоритма RSA, используемого маршрутизатором HUAWEI в Java/Kotlin
Получение разных результатов шифрования — RSA — Java и Openssl
Как программно создать неэкспортируемый закрытый ключ с сертификатом X509 в хранилище ключей Windows (C#)
Как я могу безопасно сгенерировать один и тот же закрытый ключ в JS и PHP?
Window Crypto Subtle: как мы можем использовать метод шифрования/дешифрования, если мы сгенерируем открытый закрытый ключ с помощью алгоритма «RSASSA-PKCS1-v1_5»?
Window.crypto.subtle.importkey и проблема с подписью
Внедрение CRYSTALS-Kyber с использованием BouncyCastle Java