CAVP 서비스 만들면서 작성한 RSA 키쌍 유효성 판별 코드.

문서 찾아보고 그대로 작성했을 뿐, 아직 누구에게 확인받아 본 적은 없다...

//#region RSA 키쌍의 유효성 판별

// NIST SP 800-56B r2
// (https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf)
// - 6.4.1.2.2 rsakpv1-prime-factor
public void verifyRsaKey(int bitLen, BigInteger e, BigInteger p, BigInteger q, BigInteger n, BigInteger d) {
    if (!n.equals(p.multiply(q))) {
        throw new InvalidArgumentException("n != p * q");
    }
    if (n.bitLength() != bitLen) {
        throw new InvalidArgumentException("n 의 키 길이 != " + n.bitLength());
    }
    checkPrimeFactors(bitLen, e, p, q);
    checkPrivateExponent(bitLen, e, p, q, d);
}

private void checkPrimeFactors(int bitLen, BigInteger e, BigInteger p, BigInteger q) {
    // a. If (p < (root(2) * (2^(bitLen/2−1))) or (p > 2^(bitLen/2) – 1)
    if (!checkPrimeRange(bitLen, p)) {
        throw new InvalidArgumentException("p(p1) 가 유효한 범위 내에 있지 않습니다");
    }
    // b. If (q < (root(2) * (2^(bitLen/2−1))) or (q > 2^(bitLen/2) – 1)
    if (!checkPrimeRange(bitLen, q)) {
        throw new InvalidArgumentException("q(p2) 가 유효한 범위 내에 있지 않습니다");
    }
    // c. If |p – q| <= 2(nBits/2−100)
    BigInteger difference = BigInteger.valueOf(2L * (bitLen / 2 - 100));
    if (p.subtract(q).abs().compareTo(difference) <= 0) {
        throw new InvalidArgumentException("|p - q| 가 너무 작습니다");
    }
    // d. If GCD (p – 1, epub) != 1
    if (!p.subtract(BigInteger.ONE).gcd(e).equals(BigInteger.ONE)) {
        throw new InvalidArgumentException("GCD(p-1, e) != 1");
    }
    // e. If GCD (q – 1, epub) != 1
    if (!q.subtract(BigInteger.ONE).gcd(e).equals(BigInteger.ONE)) {
        throw new InvalidArgumentException("GCD(q-1, e) != 1");
    }
    // FIPS 186-5 (https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf)
    // - Appendix B.3 Probabilistic Primality Tests
    if (!p.isProbablePrime(44)) {
        throw new InvalidArgumentException("p(p1) 가 소수가 아닙니다 (확률적 소수 판별법)");
    }
    if (!q.isProbablePrime(44)) {
        throw new InvalidArgumentException("q(p2) 가 소수가 아닙니다 (확률적 소수 판별법)");
    }
}

private void checkPrivateExponent(int bitLen, BigInteger e, BigInteger p, BigInteger q, BigInteger d) {
    if (d.compareTo(BigInteger.valueOf(2).pow(bitLen / 2)) <= 0) {
        throw new InvalidArgumentException(String.format("d(s) 가 2 ^ (%d / 2) 보다 작습니다", bitLen));
    }
    BigInteger pSub1 = p.subtract(BigInteger.ONE);
    BigInteger qSub1 = q.subtract(BigInteger.ONE);
    BigInteger gcdSub1 = pSub1.gcd(qSub1);
    BigInteger lcmSub1 = pSub1.multiply(qSub1).divide(gcdSub1);
    if (d.compareTo(lcmSub1) >= 0) {
        // NOTE(jyha) : d 가 작을수록 공격자가 비밀키를 계산하는데 어려워지기 때문에 d < LCM(p-1, q-1) 를 유지하는 것이 좋긴 하지만,
        //              일반적으로 키쌍을 생성할 때 d 의 크기까지 확인하지는 않음.
    }
    if (!d.multiply(e).mod(lcmSub1).equals(BigInteger.ONE)) {
        throw new InvalidArgumentException("(d * e) mod LCM (p-1, q-1) != 1");
    }
}

private boolean checkPrimeRange(int bitLen, BigInteger prime) {
    double doubleNum = 2 ^ (bitLen / 2 - 1);
    // (p < (root(2) * (2^(bitLen/2−1))) => false
    if (prime.compareTo(BigInteger.valueOf((long)(Math.sqrt(2) * doubleNum))) < 0) {
        return false;
    }
    // p > 2^(bitLen/2) – 1 => false
    return prime.compareTo(BigInteger.valueOf(2).pow(bitLen / 2).subtract(BigInteger.ONE)) <= 0;
}

//#endregion

+ Recent posts