모듈로 연산자 (%)는 C #의 다른 .NET 버전에 대해 다른 결과를 제공합니다.


89

비밀번호 문자열을 생성하기 위해 사용자 입력을 암호화하고 있습니다. 그러나 한 줄의 코드는 프레임 워크의 다른 버전에서 다른 결과를 제공합니다. 사용자가 누른 키 값이있는 부분 코드 :

키 누름 : 1. 변수 ascii는 49입니다. 일부 계산 후 'e'및 'n'값 :

e = 103, 
n = 143,

Math.Pow(ascii, e) % n

위 코드의 결과 :

  • .NET 3.5 (C #)

    Math.Pow(ascii, e) % n
    

    제공합니다 9.0.

  • .NET 4 (C #)

    Math.Pow(ascii, e) % n
    

    제공합니다 77.0.

Math.Pow() 두 버전 모두에서 올바른 (동일한) 결과를 제공합니다.

원인은 무엇이며 해결책이 있습니까?


12
물론 질문의 두 답변은 모두 틀 렸습니다. 당신이 그것에 대해 신경 쓰지 않는 것 같다는 사실은 걱정 스럽습니다.
David Heffernan 2014 년

34
여러 단계로 돌아 가야합니다. "암호 문자열을 생성하기 위해 사용자 입력을 암호화하고 있습니다."이 부분은 이미 모호합니다. 실제로 무엇을하고 싶습니까? 암호를 암호화되거나 해시 된 형식으로 저장 하시겠습니까? 임의의 값을 생성하기 위해 이것을 엔트로피로 사용 하시겠습니까? 보안 목표는 무엇입니까?
CodesInChaos 2014 년

49
이 질문은 부동 소수점 산술에 대한 흥미로운 문제를 설명하지만 OP의 목표가 "암호 용 문자열 생성을위한 사용자 입력 암호화"인 경우 자체 암호화를 롤링하는 것이 좋은 생각이 아니므로 권장하지 않습니다. 실제로 답변을 구현합니다.
Harrison Paine

18
다른 언어 %에서 부동 소수점 숫자 사용을 금지하는 이유를 잘 보여줍니다 .
Ben Voigt 2014 년

5
대답은 좋지만 .NET 3.5와 4 사이에서 무엇이 변경되어 다른 동작을 유발하는지에 대한 질문에는 대답하지 않습니다.
msell 2014

답변:


160

Math.Pow배정 밀도 부동 소수점 숫자에서 작동합니다. 따라서 결과 의 처음 15–17 자리 이상 이 정확할 것으로 기 대해서는 안됩니다.

또한 모든 부동 소수점 숫자에는 제한된 수의 유효 자릿수가 있으며, 이는 부동 소수점 값이 실수에 근접하는 정도를 결정합니다. Double17 자릿수의 최대 내부적으로 유지되지만 값은, 정밀도의 15 진수까지 갖는다.

그러나 모듈로 산술에서는 모든 숫자가 정확해야합니다. 귀하의 경우에는, 당신은 49 계산된다 (103) , 그 결과 175 자리 숫자로 구성되어, 모두 답의 모듈로 연산의 의미를 제작.

올바른 값을 계산하려면 BigInteger클래스 (.NET 4.0에서 도입 됨)에서 제공하는 임의 정밀도 산술을 사용해야합니다 .

int val = (int)(BigInteger.Pow(49, 103) % 143);   // gives 114

편집 : 아래 주석에서 Mark Peters가 지적했듯이 BigInteger.ModPow이러한 종류의 작업을 위해 특별히 고안된 방법을 사용해야합니다 .

int val = (int)BigInteger.ModPow(49, 103, 143);   // gives 114

20
진짜 문제를 지적 +1, 즉 문제의 코드는 명백히 잘못이다
데이비드 헤퍼 넌

36
BigInteger가이 작업에 대해 약 5 배 더 빠르게 수행하는 ModPow () 메서드를 제공한다는 점은 주목할 가치가 있습니다.
Mark Peters

8
+1 수정. ModPow는 빠를뿐만 아니라 수치 적으로도 안정적입니다!
Ray

2
@maker 아니요, 대답은 무의미 하고 유효 하지 않습니다 .
Cody Gray

3
@ makerofthings7 : 원칙적으로 동의합니다. 그러나 부정확성은 부동 소수점 산술에 내재되어 있으며 일반적으로 작업에 제한을 부과하는 것보다 개발자가 위험을 인식 할 것으로 기대하는 것이 더 실용적이라고 간주됩니다. 진정으로 "안전"하고 싶다면로 1.0 - 0.9 - 0.1 == 0.0평가하는 것과 같은 예상치 못한 결과를 피하기 위해 언어는 부동 소수점 동등성 비교를 금지해야 합니다 false.
Douglas

72

해싱 함수가별로 좋지 않다는 사실을 제외하고 * 코드의 가장 큰 문제는 .NET 버전에 따라 다른 숫자를 반환하는 것이 아니라 두 경우 모두 완전히 의미없는 숫자를 반환한다는 것입니다. 문제에 대한 정답은

49 103 = 114이 (143을 개조 볼프람 알파 링크 )

이 코드를 사용하여이 답변을 계산할 수 있습니다.

private static int PowMod(int a, int b, int mod) {
    if (b == 0) {
        return 1;
    }
    var tmp = PowMod(a, b/2, mod);
    tmp *= tmp;
    if (b%2 != 0) {
        tmp *= a;
    }
    return tmp%mod;
}

귀하의 계산이 다른 결과를 생성하는 이유는 대답을 생산하기 위해, 당신은 (49)의 유효 숫자의 대부분 떨어 중간 값 사용한다는 것입니다 (103) 에만 175 자리의 첫 번째 16이 올바른 : 수를!

1230824813134842807283798520430636310264067713738977819859474030746648511411697029659004340261471771152928833391663821316264359104254030819694748088798262075483562075061997649

나머지 159 자리는 모두 잘못되었습니다. 그러나 mod 작업은 마지막 숫자를 포함하여 모든 단일 숫자가 정확해야하는 결과를 찾습니다. 따라서 Math.Pow.NET 4에서 구현되었을 수 있는 정밀도에 대한 아주 작은 개선도 계산에 큰 차이를 가져와 본질적으로 임의의 결과를 생성합니다.

* 이 질문은 암호 해싱의 맥락에서 정수를 높은 힘으로 올리는 것에 대해 이야기하므로 현재 접근 방식을 잠재적으로 더 나은 접근 방식으로 변경해야하는지 결정하기 전에이 답변 링크 를 읽는 것이 좋습니다 .


20
좋은 대답입니다. 진짜 요점은 이것이 끔찍한 해시 함수라는 것입니다. OP는 솔루션을 재고하고 더 적절한 알고리즘을 사용해야합니다.
david.pfx 2014 년

1
Isaac Newton : 사과가 지구에 끌리는 것과 같은 방식으로 달이 지구에 끌리는 것이 가능합니까? @ david.pfx : 진짜 요점은 이것이 사과를 따는 끔찍한 방법이라는 것입니다. 뉴턴은 해결책을 재고하고 아마도 사다리가있는 사람을 고용해야합니다.
jwg

2
@jwg David의 댓글은 이유 때문에 많은 찬성표를 받았습니다. 원래의 질문은 알고리즘이 암호를 해시하는 데 사용되었고 실제로 그 목적을위한 끔찍한 알고리즘이라는 것을 분명히했습니다. 이미 입증 된 바와 같이 .NET 프레임 워크의 버전간에 중단 될 가능성이 매우 높습니다. OP가 자신의 알고리즘을 "수정"하는 대신 대체 해야한다는 점을 언급하지 않는 모든 대답 은 그에게 해를 끼치는 것입니다.
Chris

@Chris 의견을 보내 주셔서 감사합니다. David의 제안을 포함하도록 편집했습니다. 나는 OP의 시스템이 장난감이거나 자신의 즐거움을 위해 만든 코드 조각 일 수 있기 때문에 당신만큼 강하게 말하지 않았습니다. 감사!
Sergey Kalinichenko 2014

27

당신이 보는 것은 두 배의 반올림 오류입니다. Math.Pow이중으로 작동하며 차이점은 다음과 같습니다.

.NET 2.0 및 3.5 => var powerResult = Math.Pow(ascii, e);반환 :

1.2308248131348429E+174

.NET 4.0 및 4.5 => var powerResult = Math.Pow(ascii, e);반환 :

1.2308248131348427E+174

앞의 마지막 숫자 E가 결과의 차이를 유발합니다. 모듈러스 연산자가 아닙니다 (%) .


3
거룩한 암소가 OPs 질문에 대한 유일한 대답입니까? 나는 모든 메타 "blah blah security wrong question I know more than you n00b"를 읽고 여전히 "왜 3.5와 4.0 사이의 불일치가 일관된가? 달을 바라보며 바위에 발을 딛고"어떤 종류의 바위가 "당신의 진짜 문제는 발을 보지 않는 것입니다."또는 "밤에 집에서 만든 샌들을 신을 때 무엇을 기대합니까? !!!"고맙습니다!
Michael Paulukonis

1
@MichaelPaulukonis : 그건 잘못된 비유입니다. 암석 연구는 합법적 인 연구입니다. 고정 정밀도 데이터 유형을 사용하여 임의 정밀도 산술을 수행하는 것은 매우 잘못되었습니다. 나는 이것을 C #을 작성할 때 개가 고양이보다 나쁜 이유를 묻는 소프트웨어 모집 자와 비교합니다. 당신이 동물 학자라면이 질문에 약간의 장점이있을 수 있습니다. 다른 모든 사람에게는 의미가 없습니다.
Douglas

24

부동 소수점 정밀도는 시스템마다 다를 수 있으며 동일한 시스템에서도 다를 수 있습니다 .

그러나 .NET은 앱을위한 가상 머신을 생성하지만 버전마다 변경 사항이 있습니다.

따라서 일관된 결과를 생성하기 위해 의존해서는 안됩니다. 암호화의 경우 자체적으로 롤링하는 대신 프레임 워크에서 제공하는 클래스를 사용하십시오.


10

코드가 나쁜 방식에 대한 많은 답변이 있습니다. 하지만 결과가 다른 이유는…

Intel의 FPU 는 내부적으로 80 비트 형식을 사용하여 중간 결과에 대해 더 많은 정밀도를 얻습니다. 따라서 값이 프로세서 레지스터에 있으면 80 비트를 얻지 만 스택에 기록되면 64 비트에 저장됩니다 .

최신 버전의 .NET은 JIT (Just in Time) 컴파일에서 더 나은 최적화 프로그램을 가지고 있으므로 스택에 기록한 다음 스택에서 다시 읽는 대신 레지스터에 값을 유지합니다.

JIT는 이제 스택이 아닌 레지스터에서 값을 반환 할 수 있습니다. 또는 레지스터의 MOD 함수에 값을 전달합니다.

스택 오버플로 질문도 참조하십시오 . 80 비트 확장 정밀도 데이터 유형의 응용 프로그램 / 이점은 무엇입니까?

ARM과 같은 다른 프로세서는이 코드에 대해 다른 결과를 제공합니다.


6

정수 산술 만 사용하여 직접 계산하는 것이 가장 좋습니다. 다음과 같은 것 :

int n = 143;
int e = 103;
int result = 1;
int ascii = (int) 'a';

for (i = 0; i < e; ++i) 
    result = result * ascii % n;

다른 답변에 게시 된 BigInteger 솔루션의 성능과 성능을 비교할 수 있습니다.


7
이를 위해서는 103 번의 곱셈과 계수 감소가 필요합니다. e2 = e * e % n, e4 = e2 * e2 % n, e8 = e4 * e4 % n 등을 계산하면 더 잘할 수 있으며 결과 = e * e2 % n * e4 % n * e32 % n * e64 % n. 총 11 개의 곱셈과 계수 감소. 관련된 숫자의 크기를 감안할 때 모듈러스 감소를 몇 가지 더 제거 할 수 있지만 103 개 작업을 11로 줄이는 것과 비교하면 미미할 것입니다.
supercat

2
@supercat 좋은 수학이지만 실제로는 토스터에서 이것을 실행하는 경우에만 관련이 있습니다.
alextgordon

7
@alextgordon : 또는 더 큰 지수 값을 사용할 계획이라면. 지수 값을 예를 들어 65521로 확장하면 강도 감소를 사용하는 경우 약 28의 곱셈과 모듈러스 감소가 필요하지만 그렇지 않은 경우 65,520이 걸립니다.
supercat

+1은 계산이 정확히 어떻게 수행되는지 명확한 솔루션을 제공합니다.
jwg 2014

2
@Supercat : 당신이 절대적으로 옳습니다. 알고리즘을 개선하는 것은 쉽습니다. 이는 매우 자주 계산되거나 지수가 큰 경우에 적합합니다. 그러나 주요 메시지는 정수 산술을 사용하여 계산할 수 있고 계산해야한다는 것입니다.
Ronald
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.