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

일하다가 매번 용도에 맞는 샘플 코드 작성해서 돌려보기 귀찮아서 만듬

https://webca.app/

  • Crypto & PKI : 블록암호, 공개키암호, 해시, 전자서명, 키 유도, PKCS#7, PKCS#8 등의 기능 제공 (일하면서 필요했던 것 위주로)
  • CAVP : CAVP 데이터 생성 및 검증 기능 제공 (양심적으로 생성 기능을 제출 용도로 사용하진 말자...)

인증서 생성 기능은 각종 옵션들을 front-end 소스로 만들기 어려워서 보류 중... '+' 클릭하면 해당 필드 생기는 걸 하고 싶은데 모르겠고, 이제 내 업무에 CA 는 없는데 굳이 그렇게까지 해야하나 싶고...

그래도 샘플 인증서가 필요한 경우들이 있으니 하긴 해야겠지?

 

예전에 한과장님 '에라토스테네스의 체'를 설명할 때는 수학에 '체'가 무슨 말인가? 했었는데, 이 Animated GIF를 보고 한번에 이해됐다.


1부터 120 사이에 있는 소수를 구하는 방식인데, 소수인지 판별하는 부분이 아니라 이미 소수로 판정이 된 수의 배수를 걸러내는 방식이다.



# n | a
  - a는 n의 배수이다.
 
# a mod n
  - a를 n으로 나누었을 때 나머지 값
 
# a ≡ b mod n
  - a와 b는 n으로 나누었을 때 그 나머지가 같다. (a와 b는 합동이다)
 
# 모듈러 연산의 특성
  - a ≡ b mod n 이면, a - b는 n의 배수이다. -> (a - b) mod n = 0
  - mod n = Zn = { 0, 1, ..., n - 1 }
 
# 모듈러 연산 법칙
  - [(a mod n) + (b mod n)] mod n = (a + b) mod n
  - [(a mod n) - (b mod n)] mod n = (a - b) mod n
  - [(a mod n) x (b mod n)] mod n = (a x b) mod n
 
# Galois Fields - GF(p) [아직 명확히 이해 못함. 위수가 단순히 원소의 개수인 것인지... 유한체는 뭔지...]
  - 소수 p에 대하여 위수가 p인 유한체
  - GF(p)는 정수 { 0, 1, ..., p-1 }의 집합 Zp로 정의
  - p가 소수이므로 Zn상의 모든 정수들에 대해 곱셈의 역원이 존재함

회사에서 세미나하는 내용인데 번개같이 넘어가는 바람에 뭔말인지 모르겠어요. ㅋㅋㅋ
이해도 할겸 복습하며 예문 위주로 정리합니다.
내가 이해한대로 정리한거라 틀릴수도 있으니 책임은 못 집니다.

# GCD(a, b) : a와 b의 최대 공약수 (Gratest Common Divisor)

ex) GCD(49, 21) = 7

# 서로소 : GCD(a, b) = 1 인 정수 a와 b를 서로소라 한다

ex) GCD(5, 7) = 1
ex) GCD(6, 11) = 1
ex) GCD(10, 21) = 1

# 소수 : 1 과 자신만으로 나누어지는 1보다 큰 양의 정수

ex) 2, 3, 5, 7, 11, 13, ...

# 합성수 : 소수가 아닌 양의 정수 (1과 자신 이외에 다른 값으로 나누어지는 자연수)

ex) 4, 6, 8, 9, 10, 12, 14, 15, ...

# 모든 정수(n > 1)는 소수의 곱만으로 표현할 수 있으며, 순서를 무시한다면 그 표현방법은 유일하다.

ex) 15 = 3 x 5
ex) 50 = 2 x 25 = 2 x 5 x 5
ex) 100 = 2 x 50 = 2 x 2 x 5 x 5

# 유클리드 알고리즘 : a = bq + r 이면 GCD(a, b) = GCD(b, r)

ex) 49 = 28 x 1 + 21 -> GCD(49, 28) = GCD(28, 21)
ex) 54 = 14 x 3 + 12 -> GCD(54, 14) = GCD(14, 12)

※ a를 b로 나눈 나머지(r)는 항상 최대 공약수의 배수이다. 따라서 b와 r의 최대공약수도 동일하다.

# 유클리드 호제법(互除法 : 번갈아가며 제거하는 방법)

유클리드 알고리즘을 반복적으로 적용하여 최대공약수를 구하는 방법

※ 나머지가 0 이면 작은 값이 최대 공약수

최대공약수 나머지 찾기
GCD(1020, 790) 1020 = 1 x 790 + 230
GCD(790, 230) 790 = 3 x 230 + 100
GCD(230, 100) 230 = 2 x 100 + 30
GCD(100, 30) 100 = 3 x 30 + 10
GCD(30, 10) 30 = 3 x 10 + 0

# 베주의 정의 (bezout's identity)

동시에 0이 아닌 두 정수 a, b에 대해서 GCD(a, b) = ax + by를 만족하는 정수 x, y가 존재한다.

# 확장 유클리드 알고리즘 : 베주의 정의 GCD(a, b) = ax + by의 해(x, y)를 구하는 방법

위 유클리드 호제법 풀이의 나머지 값 외에는 a, b를 대입할 수 있다.

이를 이용해 나머지가 최소공약수가 될 때 까지 나머지를 a와 b로 표현하는 방식으로 정리한다.

ex) 위 유클리드 호제법을 사용한 예제. 위 호제법 풀이의 오른쪽 풀어놓은 식에 대입

  • 1020 = 1 x 790 + 230 (위 유클리드 호제법의 풀이에서 가져온 식)
    • a = 1 x b + 230 (1020 = a, 790 = b)
    • 230 = a - (1 x b) = a - b (호제법의 나머지인 230을 기준으로 정리)
    • 따라서, 230 = a - b
  • 790 = 3 x 230 + 100 (위 유클리드 호제법의 풀이에서 가져온 식)
    • b = 3 x (a - b) + 100 (790 = b, 230 = a - b)
    • 100 = b - (3 x (a - b)) = b - (3a - 3b) = -3a + 4b (호제법의 나머지인 100을 기준으로 정리)
    • 따라서, 100 = -3a + 4b
  • 230 = 2 x 100 + 30 (위 유클리드 호제법의 풀이에서 가져온 식)
    • a - b = 2 x (-3a + 4b) + 30 (230 = a - b, 100 = -3a + 4b)
    • 30 = a - b - (2 x (-3a + 4b) = a - b - (-6a + 8b) = 7a - 9b (호제법의 나머지인 30을 기준으로 정리)
    • 따라서, 30 = 7a - 9b
  • 100 = 3 x 30 + 10 (위 유클리드 호제법의 풀이에서 가져온 식)
    • -3a + 4b = 3 x (7a - 9b) + 10 (100 = -3a + 4b, 30 = 7a - 9b)
    • 10 = -3a + 4b - (3 x (7a -9b)) = -3a + 4b - (21a - 27b) = -24a + 31b (호제법의 나머지인 10을 기준으로 정리)
    • 따라서, 10 = -24a + 31b
  • 10 은 GCD(1020, 790)의 해, 즉 최대공약수이므로 a와 b에 원래의 값을 대입하면
    • GCD(1020, 790) = -24 x 1020 + 31 x 790
    • a = 1020, b = 790 일 경우 GCD(a, b) = ax + by 를 만족하는 x와 y는

x = -24, y = 31

확인 : 1020 x -24 + 790 x 31 = -24480 + 24490 = 10

일단 용어 정리부터...
 
비밀키 : 일반적인 암호화에 사용되는 키. A와 B는 같은 비밀키를 공유해야 한다.
공개키 : 공개키 알고리즘에서 사용되는 키로 외부에 공개되는 키
개인키 : 공개키 알고리즘에서 사용되는 키로 개인이 소유하며 외부에 알려주지 않는 키
 
목적 - 공개키 알고리즘을 통해 비밀키를 생성하여 암호화에 사용한다.
 
DH 알고리즘의 핵심은 상대방의 공개키와 나의 개인키를 이용하여 계산을 하면 비밀키가 나온다는 것이다.
그 후 나와 상대방은 비밀키를 사용하여 데이터를 암호화한 후 전달하면 된다.
 
즉, AB 사이에서 사용할 비밀키를 생성하는 것을 식으로 나타내면 다음과 같다.
A의 비밀키 = A의 개인키 [DH연산] B의 공개키
B의 비밀키 B의 개인키 [DH연산] A의 공개키
그리고 A의 비밀키 = B의 비밀키 
 
위 DH연산을 사용하면 A의 비밀키와 B의 비밀키는 같은 값이 나오기 때문에 이것을 암호화에 사용할 수 있는 것이다.
그럼 DH에서 사용하는 연산은 이런 조건을 만족한다는 뜻인데 이 연산은 어떤 것일까?
 
여기서 이번엔 기호 정리...
 
p = DH연산에 사용되는 소수 (1024 bits 이상)
g = DH연산에 사용되는 베이스, 특별한 수. (x1의 값에 따라 나오는 개인키의 종류가 정해진 g는 사용할 수 없다)
s = 비밀키
 
A의 작업
========
1. 임의의 개인키 x1을 생성하고 다음의 식으로 공개키(y1)를 계산한다.
   y1 = gx1 mod p
2. B에게 자신의 공개키 y1을 전달한다.
B의 작업
========
3. 임의의 개인키 x2를 생성하고 다음의 식으로 공개키(y2)를 계산한다.
   y2 = gx2 mod p
4. 전달받은 A의 공개키(y1)와 자신의 개인키(x2)를 이용하여 비밀키(s)를 계산한다.
   s = y1x2 mod p
5. A에게 자신의 공개키 y2를 전달한다.
A의 작업
========
6. 전달받은 B의 공개키(y2)와 자신의 개인키(x1)를 이용하여 비밀키(s)를 계산한다.
   s = y2x1 mod p
 
위의 결과에서 보면 비밀키(s)가 같으려면 다음의 공식도 만족해야 한다.
   y1x2 mod p = y2x1 mod p
위 공식에 y1과 y2를 생성할 때 사용했던 계산식을 넣어보자.
   (gx1 mod p)x2 mod p = (gx2 mod p)x1 mod p
 
여기서 더 나아가려면 다른 것에 대해서 먼저 알아야되겠다.
((x mod p) * (y mod p)) mod p = xy mod p
 
따라서
(gx1 mod p)x2 mod p 는 (gx1)x2 mod p 가 되고
(gx2 mod p)x1 mod p 는 (gx2)x1 mod p 가 된다.
 
그럼 (gx1)x2 mod p = (gx2)x1 mod p 가 되는데, 이정도 왔으면 왜 같은지 알 것이다. ^^;;
지수승은 곱으로 대체할 수 있으니
gx1x2 mod p = gx2x1 mod p 가 된다.
 
끝났다! 

아.. 제길... 이거 쓴다고 야근한 꼴이 되는구나...
((x mod p) * (y mod p)) mod p = xy mod p
이거 증명하려다가 시간 다 보냈다.. ㅠ_ㅠ
결국 증명은 제외 ㅎㅎ


싸이 블로그 백업 [하지윤 2008.10.22 21:31]

// 상대방과 p와 g를 같은 값을 사용하고 상대방의 공개키를 알고 있는 상황
// p와 g와 자신의 개인키가 포함된 DH* pDH, 상대방의 공개키 BIGNUM* pPubKey
unsigned char abyKey[128]; // 128bit 키라면...
DH_compute_key(abyKey, pPubKey, pDH);


싸이 블로그 백업 [하지윤 2008.10.22 18:08]

// pDH에 이미 pDH->p, pDH->g가 세팅되어 있어야 함.
DH_generate_key(pDH);
// pDH->pub_key, pDH->pri_key 사용


싸이 블로그 백업 [하지윤 2008.10.22 18:00]

int nDHRet = DH_generate_parameters_ex(pDH, 1024, DH_GENERATOR_2, &g_cb); // 1024 bit에 g는 2로 생성.
if(!DH_check(pDH, &nDHRet))
{
   return;
}
if(nDHRet & DH_CHECK_P_NOT_PRIME)
   printf("p value is not prime\n");
if(nDHRet & DH_CHECK_P_NOT_SAFE_PRIME)
   printf("p value is not a safe prime\n");
if(nDHRet & DH_UNABLE_TO_CHECK_GENERATOR)
   printf("unable to check the generator value\n");
if(nDHRet & DH_NOT_SUITABLE_GENERATOR)
   printf("the g value is not a generator\n");
if(nDHRet == 0)
   printf("DH parameters appear to be ok.\n");

int nPBits = BN_num_bits(pDH->p); // p의 bit 크기 : 1024
int nGBits = BN_num_bits(pDH->g); // g의 bit 크기 : 2 (g가 5일땐 3)


싸이 블로그 백업 [하지윤 2008.10.22 17:55]

p가 소수이고, m이 p와 서로소인 양수일 때, (페르마의 정리)
m(p-1)  mod p = 1 
 
p와 q가 소수일 때, n = p*q, 그리고 m과 n은 서로소일 경우, (오일러의 공식)
m(p-1) (q-1) mod n = 1 
 
이것이 어떻게 사용되는지도 조만간...


싸이 블로그 백업 [하zi 2008.07.14 19:03]

+ Recent posts