Microsoft MVP성태의 닷넷 이야기
.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기 [링크 복사], [링크+제목 복사],
조회: 25034
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 14개 있습니다.)
.NET Framework: 292. RSACryptoServiceProvider의 공개키와 개인키 구분
; https://www.sysnet.pe.kr/2/0/1218

.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기
; https://www.sysnet.pe.kr/2/0/1295

.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자
; https://www.sysnet.pe.kr/2/0/1300

.NET Framework: 356. (공개키를 담은) 자바의 key 파일을 닷넷의 RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1401

.NET Framework: 383. RSAParameters의 ToXmlString과 ExportParameters의 결과 비교
; https://www.sysnet.pe.kr/2/0/1491

.NET Framework: 565. C# - Rabin-Miller 소수 생성 방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10925

.NET Framework: 566. openssl의 PKCS#1 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/10926

.NET Framework: 638. RSAParameters와 RSA
; https://www.sysnet.pe.kr/2/0/11140

.NET Framework: 1037. openssl의 PEM 개인키 파일을 .NET RSACryptoServiceProvider에서 사용하는 방법 (2)
; https://www.sysnet.pe.kr/2/0/12598

.NET Framework: 2093. C# - PKCS#8 PEM 파일을 이용한 RSA 개인키/공개키 설정 방법
; https://www.sysnet.pe.kr/2/0/13245

닷넷: 2297. C# - ssh-keygen으로 생성한 Public Key 파일 해석과 fingerprint 값(md5, sha256) 생성
; https://www.sysnet.pe.kr/2/0/13739

닷넷: 2297. C# - ssh-keygen으로 생성한 ecdsa 유형의 Public Key 파일 해석
; https://www.sysnet.pe.kr/2/0/13742

닷넷: 2300. C# - OpenSSH의 공개키 파일에 대한 "BEGIN OPENSSH PUBLIC KEY" / "END OPENSSH PUBLIC KEY" PEM 포맷
; https://www.sysnet.pe.kr/2/0/13747

닷넷: 2302. C# - ssh-keygen으로 생성한 Private Key와 Public Key 연동
; https://www.sysnet.pe.kr/2/0/13749




RSAParameters 와 System.Numerics.BigInteger 이야기

(2024-09-29 업데이트) .NET Core/5+ 환경이라면, 새롭게 업데이트된 다음의 글을 읽어볼 것을 권장합니다.

C# - BigInteger 타입이 byte 배열로 직렬화하는 방식
; https://www.sysnet.pe.kr/2/0/13748#rsa_key





RSAParameters는 다음과 같이 간단한 구조체에 불과합니다.

[Serializable, StructLayout(LayoutKind.Sequential), ComVisible(true)]
public struct RSAParameters
{
    public byte[] Exponent;
    public byte[] Modulus;
    [NonSerialized]
    public byte[] P;
    [NonSerialized]
    public byte[] Q;
    [NonSerialized]
    public byte[] DP;
    [NonSerialized]
    public byte[] DQ;
    [NonSerialized]
    public byte[] InverseQ;
    [NonSerialized]
    public byte[] D;
}

RSA에 대해서 아시거나... 혹은 다음의 도움말을 참고해보면,

RSAParameters Structure
; https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsaparameters

아래의 공식이 성립하는 것을 볼 수 있습니다.

Modulus == D * Q

그럼, 정말로 그런지 한번 테스트 해볼까요? 이를 확인하기 위해 System.Security.Cryptography.RSAParameters 타입을 생성하면 되는데,

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
Console.WriteLine(rsa.KeySize); // 1024

System.Security.Cryptography.RSAParameters rsaParam = rsa.ExportParameters(true);

여기서 "KeySize == Modulus 비트 수"입니다. RSACryptoServiceProvider 타입은 (Microsoft Enhanced Cryptographic Provider에 의해) 기본적으로 1024 길이의 비트를 생성하기 때문에, 따라서 Modulus 값은 (1024 / 8) == 128바이트의 배열이 되고, 소수인 D 와 Q 값은 64바이트로 구성됩니다. 그렇다면, 64바이트 값을 어떻게 숫자로 표현할 수 있을까요? int == 4바이트, long == 8바이트이기 때문에 이런 경우는 .NET 4.0에서 새롭게 제공되는 System.Numerics.BigInteger 타입을 이용해야 합니다.

그렇게 해서 최종적으로 검증해 보면 다음과 같이 나옵니다.

var rsaParam = rsa.ExportParameters(true);

BigInteger p = new BigInteger(rsaParam.P);
BigInteger q = new BigInteger(rsaParam.Q);

Console.WriteLine("P == " + p);
Console.WriteLine();
Console.WriteLine("Q == " + q);
Console.WriteLine();
Console.WriteLine();

BigInteger expectModulus = p * q;
BigInteger realModulus = new BigInteger(rsaParam.Modulus);

Console.WriteLine("expect == " + expectModulus);
Console.WriteLine();
Console.WriteLine("real == " + realModulus);
Console.WriteLine();
Console.WriteLine();

Console.WriteLine(expectModulus == realModulus);

// 출력 결과
P == 1745347117586124307562205124286981362442227763277912640750731532696045914361892339770449856387948878818154992652330479951788437061857370883395881528451063

Q == -2831377943566696349004974146379573665554872347974010564465244410976940341475835118677977005548443979634625788268996007446130714733671878050920424168711433


expect == -4941737332601061606570187639549427718961295189156172733040629026496217387840960517507341781860167036894747005869475064044704332300007926725569534038965955079267069563372191094586565054803509844616827366332886219041144233218481874255267563142125606547422977188664512747390151137223352939005270173921109103279

real == -16006374138550337361913895556834746249117422527852669572216124554020516563250980797049053677292949829318290649342785628587299165879371350772513931531074679499167095600766366968566255398032852895784920219487284111177709190850812490257181200566885064876772887181829227853031677929745974169705512945745218126097

False

솔직히, 위의 결과를 보고 매우 당혹스러웠는데요. codeproject에서 다음의 클래스를 다운로드해서 다시 검증을 시도해 보면서 희미하게 나마 길이 보이기 시작했습니다.

C# BigInteger Class
; http://www.codeproject.com/Articles/2728/C-BigInteger-Class

P == 13166127462085329373727425166872120986194216491356932086431169554619716019875387246531996148969249129949565580040266682598776454158918312549723959628032699

Q == 11390141563278975215819305956455532350216609406733079257245450072905569011882636528766209599715416605839390753590072712761658668466252783809241838580216743


expect == 149964055633326840002477251389683051260311657686489276520978618664425141749178160915585112396507001611162179675210170283871705549806201579779103084984128959100324337444518674590820398847138520023398114796723776140324034040066128137956589979334949391106309587487953021118120395908293916435156067301386111279357

real == 149964055633326840002477251389683051260311657686489276520978618664425141749178160915585112396507001611162179675210170283871705549806201579779103084984128959100324337444518674590820398847138520023398114796723776140324034040066128137956589979334949391106309587487953021118120395908293916435156067301386111279357


True

세상에... .NET 4.0 클래스에서는 예기치 않은 결과가 나온 반면, codeproject에서 다운로드한 BigInteger는 정상적으로 검증이 된 것입니다.

도대체, System.Numerics.BigInteger에 어떤 차이점이 있는 걸까요?




차이를 확인하기 위해 다음과 같이 간단한 배열을 사용해서 초기화를 해봤습니다.

{
    byte[] t1 = new byte[] { 0xF0, 0, 0, 0, 0, 0, 0, 0, 0 }; // 0xF0 == 240

    BigInteger b1 = new BigInteger(t1);
    Console.WriteLine("codeproject BigInteger == " + b1);
    System.Numerics.BigInteger nb1 = new System.Numerics.BigInteger(t1);
    Console.WriteLine(".NET 4.0 BigInteger == " + nb1);
}

// 출력 결과
codeproject BigInteger == 4427218577690292387840
.NET 4.0 BigInteger == 240

짐작하시겠지요? 이것을 기반으로 도움말을 찾아보니,

BigInteger Constructor (Byte[])
; https://learn.microsoft.com/en-us/dotnet/api/system.numerics.biginteger.-ctor

Little endian 이야기가 나옵니다.

The individual bytes in the value array should be in little-endian order

아하... 그렇다면, RSAParameters의 인자에 기록되어 있던 모든 바이트 배열은 Big Endian 식으로 기록한 값이라는 의미가 되는군요.

따라서, System.Numerics.BigInteger에서 RSAParameters의 개별값들을 정상적으로 인식시키기 위해서는 역순 배열을 해주어야 합니다.

그런데, 차이점은 그뿐만이 아닙니다. 위의 예제 코드를 다음과 같이 바꿔보면 알 수 있는데요.

{
    byte[] t1 = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0xF0 }; // 0xF0 == 240

    BigInteger b1 = new BigInteger(t1);
    Console.WriteLine("codeproject BigInteger == " + b1);
    System.Numerics.BigInteger nb1 = new System.Numerics.BigInteger(t1);
    Console.WriteLine(".NET 4.0 BigInteger == " + nb1);

    byte[] nb1Bytes = nb1.ToByteArray();
}

codeproject BigInteger == 240
.NET 4.0 BigInteger == -295147905179352825856

원했던 결과는 4427218577690292387840인데, 엉뚱하게 -295147905179352825856라는 음수값이 나온 것입니다. System.Numerics.BigInteger와 codeproject의 BigInteger 간의 두 번째 차이점이 바로 '음수 표현'입니다.

System.Numerics.BigInteger는, 2의 보수표현에 의한 signed 지원을 하고 있는 반면 codeproject의 BigInteger 값은 -1을 표현하려면 280개의 255 값을 가진 바이트 배열이 필요합니다. (정확히 그 수가 아니면 unsigned BigInteger로 표현을 합니다.)




자... 그럼, 대강 준비가 모두 끝났습니다.

위의 내용에 기반해서 RSAParameters의 값들을 설명해 보면, 모두 unsigned에다가 BigEndian으로 표현되어 있다는 점입니다.

따라서, 최종적으로 아래와 같이 코드를 만들어 주면, Modulus == P * Q 식을 검증할 수 있게 됩니다.

{
    List<byte> pList = new List<byte>();
    pList.Add(0);
    pList.AddRange(rsaParam.P);
    pList.Reverse();

    List<byte> qList = new List<byte>();
    qList.Add(0);
    qList.AddRange(rsaParam.Q);
    qList.Reverse();

    System.Numerics.BigInteger p = new System.Numerics.BigInteger(pList.ToArray());
    System.Numerics.BigInteger q = new System.Numerics.BigInteger(qList.ToArray());

    Console.WriteLine("P == " + p);
    Console.WriteLine();
    Console.WriteLine("Q == " + q);
    Console.WriteLine();
    Console.WriteLine();

    System.Numerics.BigInteger expectModulus = p * q;

    List<byte> modList = new List<byte>();
    modList.Add(0);
    modList.AddRange(rsaParam.Modulus);
    modList.Reverse();

    System.Numerics.BigInteger realModulus = new System.Numerics.BigInteger(modList.ToArray());

    Console.WriteLine("expect == " + expectModulus);
    Console.WriteLine();
    Console.WriteLine("real == " + realModulus);
    Console.WriteLine();
    Console.WriteLine();

    Console.WriteLine(expectModulus == realModulus);
}

// 출력 결과
P == 13122761769973223728438748421120966940822111822658737846751726458201009787842568183226633956353767366852373536207830897524429703477826413101329242841991559

Q == 12143591235989542801897500592593786940177231874673557922727626132792109878234051570051285301506807193717599879021393783668476782936432195855587786810886413


expect == 159357454821825460303855513529351666165351910023005057152558643457380993169002523181064440400043956860957806683852053087023978010188144107163145164586420445263064724930686003901511627395052659481408547123871407008485127664685784237943462320106826498735373812869875643428632125173297462470986055646835053787867

real == 159357454821825460303855513529351666165351910023005057152558643457380993169002523181064440400043956860957806683852053087023978010188144107163145164586420445263064724930686003901511627395052659481408547123871407008485127664685784237943462320106826498735373812869875643428632125173297462470986055646835053787867


True

역시... 이렇게 확인이 되어야 마음이 놓인다니까요. ^^

첨부한 파일은 위의 과정을 테스트한 프로젝트입니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/29/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...
NoWriterDateCnt.TitleFile(s)
11484정성태4/11/201824733.NET Framework: 737. C# - async를 Task 타입이 아닌 사용자 정의 타입에 적용하는 방법파일 다운로드1
11483정성태4/10/201828034개발 환경 구성: 358. "Let's Encrypt"에서 제공하는 무료 SSL 인증서를 IIS에 적용하는 방법 (2) [1]
11482정성태4/10/201820479VC++: 126. CUDA Core 수를 알아내는 방법
11481정성태4/10/201832097개발 환경 구성: 357. CUDA의 인덱싱 관련 용어 - blockIdx, threadIdx, blockDim, gridDim
11480정성태4/9/201822143.NET Framework: 736. C# - API를 사용해 Azure에 접근하는 방법 [2]파일 다운로드1
11479정성태4/9/201817760.NET Framework: 735. Azure - PowerShell로 Access control(IAM)에 새로운 계정 만드는 방법
11478정성태11/8/201920004디버깅 기술: 115. windbg - 덤프 파일로부터 PID와 환경변수 등의 정보를 구하는 방법 [1]
11477정성태4/8/201817459오류 유형: 460. windbg - sos 명령어 수행 시 c0000006 오류 발생
11476정성태4/8/201819025디버깅 기술: 114. windbg - !threads 출력 결과로부터 닷넷 관리 스레드(System.Threading.Thread) 객체를 구하는 방법
11475정성태3/28/201821325디버깅 기술: 113. windbg - Thread.Suspend 호출 시 응용 프로그램 hang 현상에 대한 덤프 분석
11474정성태3/27/201819438오류 유형: 459. xperf: error: TEST.Event: Invalid flags. (0x3ec).
11473정성태3/22/201824590.NET Framework: 734. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상파일 다운로드2
11472정성태3/22/201818560개발 환경 구성: 356. GTX 1070, GTX 960, GT 640M의 cudaGetDeviceProperties 출력 결과
11471정성태3/20/201821941VC++: 125. CUDA로 작성한 RGB2RGBA 성능 [1]파일 다운로드1
11470정성태3/20/201824092오류 유형: 458. Visual Studio - CUDA 프로젝트 빌드 시 오류 C1189, expression must have a constant value
11469정성태3/19/201817114오류 유형: 457. error MSB3103: Invalid Resx file. Could not load file or assembly 'System.Windows.Forms, ...' or one of its dependencies.
11468정성태3/19/201816638오류 유형: 456. 닷넷 응용 프로그램 실행 시 0x80131401 예외 발생
11467정성태3/19/201816082오류 유형: 455. Visual Studio Installer - 업데이트 실패
11466정성태3/18/201817230개발 환경 구성: 355. 한 대의 PC에서 2개 이상의 DirectX 게임을 실행하는 방법
11463정성태3/15/201819569.NET Framework: 733. 스레드 간의 read/write 시에도 lock이 필요 없는 경우파일 다운로드1
11462정성태3/14/201822428개발 환경 구성: 354. HTTPS 호출에 대한 TLS 설정 확인하는 방법 [1]
11461정성태3/13/201825054오류 유형: 454. 윈도우 업데이트 설치 오류 - 0x800705b4 [1]
11460정성태3/13/201817538디버깅 기술: 112. windbg - 닷넷 메모리 덤프에서 전역 객체의 내용을 조사하는 방법
11459정성태3/13/201818359오류 유형: 453. Debug Diagnostic Tool에서 mscordacwks.dll을 찾지 못하는 문제
11458정성태2/21/201819334오류 유형: 452. This share requires the obsolete SMB1 protocol, which is unsafe and could expose your system to attack. [1]
11457정성태2/17/201824053.NET Framework: 732. C# - Task.ContinueWith 설명 [1]파일 다운로드1
... 91  92  93  94  95  96  97  [98]  99  100  101  102  103  104  105  ...