From 43e37d484d9b4ce5561397355f0a3c373eeaad3b Mon Sep 17 00:00:00 2001 From: Morgan_KR Date: Sun, 1 Aug 2021 04:38:25 +0900 Subject: [PATCH] 'asdf' --- elas.py | 144 +++++++++--- lsh/__init__.py | 30 +++ lsh/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 1528 bytes lsh/__pycache__/hmac_lsh.cpython-39.pyc | Bin 0 -> 3124 bytes lsh/__pycache__/lsh256.cpython-39.pyc | Bin 0 -> 5116 bytes lsh/__pycache__/lsh512.cpython-39.pyc | Bin 0 -> 7678 bytes lsh/__pycache__/lsh_digest.cpython-39.pyc | Bin 0 -> 2090 bytes lsh/__pycache__/lsh_template.cpython-39.pyc | Bin 0 -> 6264 bytes lsh/hmac_lsh.py | 115 ++++++++++ lsh/lsh256.py | 139 ++++++++++++ lsh/lsh512.py | 194 ++++++++++++++++ lsh/lsh_digest.py | 63 +++++ lsh/lsh_template.py | 240 ++++++++++++++++++++ main.py | 0 main.spc | 2 + stackp.cpp | 104 +++++++++ stackp.exe | Bin 0 -> 96855 bytes 17 files changed, 1003 insertions(+), 28 deletions(-) create mode 100644 lsh/__init__.py create mode 100644 lsh/__pycache__/__init__.cpython-39.pyc create mode 100644 lsh/__pycache__/hmac_lsh.cpython-39.pyc create mode 100644 lsh/__pycache__/lsh256.cpython-39.pyc create mode 100644 lsh/__pycache__/lsh512.cpython-39.pyc create mode 100644 lsh/__pycache__/lsh_digest.cpython-39.pyc create mode 100644 lsh/__pycache__/lsh_template.cpython-39.pyc create mode 100644 lsh/hmac_lsh.py create mode 100644 lsh/lsh256.py create mode 100644 lsh/lsh512.py create mode 100644 lsh/lsh_digest.py create mode 100644 lsh/lsh_template.py create mode 100644 main.py create mode 100644 main.spc create mode 100644 stackp.cpp create mode 100644 stackp.exe diff --git a/elas.py b/elas.py index 8634ffc..e9b8730 100644 --- a/elas.py +++ b/elas.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from random import randint - +from random import randrange +import hashlib +import base64 @dataclass class PrimeGaloisField: @@ -118,7 +120,7 @@ class Point: return self.__class__( x=x3.value, y=y3.value, - curve=secp256k1 + curve=self.curve ) if self == other and self.y == inf: @@ -134,7 +136,7 @@ class Point: return self.__class__( x=x3.value, y=y3.value, - curve=secp256k1 + curve=self.curve ) def __rmul__(self, scalar: int) -> "Point": @@ -158,10 +160,8 @@ class Signature: s_inv = pow(self.s, -1, N) u = (z * s_inv) % N v = (self.r * s_inv) % N - return (u*G + v*pub_key).x.value == self.r - @dataclass class PrivateKey: secret: int @@ -176,34 +176,122 @@ class PrivateKey: return Signature(r, s) +def sha256(msg: str): + hash = int('0x'+hashlib.sha256(msg.encode()).hexdigest(), 16) + return hash + +def sha1(msg: str): + hash = int('0x'+hashlib.sha1(msg.encode()).hexdigest(), 16) + return hash + +def lsh(msg: str): + from lsh import LSHDigest + lsh = LSHDigest.getInstance(256, 256) + lsh.update(msg.encode()) + hash = lsh.final() + return hex(int.from_bytes(hash,'big')) + +def largePrime(bit): + def rand(n): + return randrange(2**(n-1)+1, 2**n-1) + + def gLLP(n): + while True: + + # Obtain a random number + prime_candidate = rand(n) + + for divisor in first_primes_list: + if prime_candidate % divisor == 0 and divisor**2 <= prime_candidate: + break + # If no divisor found, return value + else: return prime_candidate + + def iMRP(miller_rabin_candidate): + maxDivisionsByTwo = 0 + evenComponent = miller_rabin_candidate-1 + + while evenComponent % 2 == 0: + evenComponent >>= 1 + maxDivisionsByTwo += 1 + assert(2**maxDivisionsByTwo * evenComponent == miller_rabin_candidate-1) + + def trialComposite(round_tester): + if pow(round_tester, evenComponent, miller_rabin_candidate) == 1: + return False + for i in range(maxDivisionsByTwo): + if pow(round_tester, 2**i * evenComponent, miller_rabin_candidate) == miller_rabin_candidate-1: + return False + return True + + # Set number of trials here + numberOfRabinTrials = 20 + for i in range(numberOfRabinTrials): + round_tester = randrange(2, miller_rabin_candidate) + if trialComposite(round_tester): + return False + return True + + first_primes_list = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, + 71, 73, 79, 83, 89, 97, 101, 103, + 107, 109, 113, 127, 131, 137, 139, + 149, 151, 157, 163, 167, 173, 179, + 181, 191, 193, 197, 199, 211, 223, + 227, 229, 233, 239, 241, 251, 257, + 263, 269, 271, 277, 281, 283, 293, + 307, 311, 313, 317, 331, 337, 347, 349] + + while True: + n = bit + prime_candidate = gLLP(n) + if not iMRP(prime_candidate): + continue + else: + return prime_candidate + break + +def b64e(data: int): + base64_bytes = base64.b64encode(bytes.fromhex(str(data).replace('0x',''))) + base64_message = base64_bytes.decode('ascii') + return base64_message if __name__ == "__main__": - P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F - A = 0 - B = 7 - - field = PrimeGaloisField(prime=P) - secp256k1 = EllipticCurve(a=A, b=B, field=field) - - G = Point( - x=0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, - y=0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, - curve=secp256k1) - - N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - - I = Point(x=None, y=None, curve=secp256k1) + ######[ SEC-P256-r1 ]##################################################### + P = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + A = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC + B = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B + N = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 inf = float("inf") - priv: int = 0xea11d6ada978a0b491aa5cbbe4df17a65c2fecc24448e95d1ccd854b43991bec - e = PrivateKey(priv) + field = PrimeGaloisField(prime=P) + secp256r1 = EllipticCurve(a=A, b=B, field=field) - pub = e.secret * G - print(pub) - z = 0x7e240de74fb1ed08fa08d38063f6a6a91462a815 + G = Point( + x=0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, + y=0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5, + curve=secp256r1) + I = Point(x=None, y=None, curve=secp256r1) + + ##################################################################### + + priv = 0x9f07090a27c7f3eaf51980059cae33420865890c72d51a8d3a20fee02c82afc63ab79c604ec6b691b94bc288b910327cd38cce7f11b61ab330b9b506c149722f + #largePrime(512) + msg = '' - signature: Signature = e.sign(z) - print(e.sign(z)) - assert signature.verify(z, pub) \ No newline at end of file + z = sha256(msg) + + e = PrivateKey(priv) + pub = e.secret * G + + signature = e.sign(z) + # print(signature.verify(z, pub)) + + f_pubKey = hex(int(f'0x40{str(pub.x).replace("0x","")}{str(pub.y).replace("0x","")}',16)) + f_privKey = hex(priv) + f_msg = hex(z) + f_Sign = hex(int(f'0x30450220{str(hex(e.sign(z).r)).replace("0x","")}022100{str(hex(e.sign(z).s)).replace("0x","")}',16)) + + print(b64e(f_pubKey)) \ No newline at end of file diff --git a/lsh/__init__.py b/lsh/__init__.py new file mode 100644 index 0000000..8969bed --- /dev/null +++ b/lsh/__init__.py @@ -0,0 +1,30 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +__all__ = ['lsh_digest', 'lsh256', 'lsh512', 'hmac_lsh'] + +from .lsh_digest import LSHDigest +from .lsh256 import LSH256 +from .lsh512 import LSH512 +from .hmac_lsh import HmacLSH diff --git a/lsh/__pycache__/__init__.cpython-39.pyc b/lsh/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc5c5fcbe5f533f70f5ec1577311297d56ae8dd7 GIT binary patch literal 1528 zcmZ8hO^@S55OwA&8Ef~-xiqJVWQCoPb`L9r@FdQ}!m%UU*c}1KhdtM{+})5VJ?_yza>iANlB1S697yuiB~j=~G?9YkK+HXPskB`(1AS z{W`$rBYcHiQf;Cww6yU zu$zI_ZpEJ-X_`)5zS>s#BCF{Z~ZJl%)v1*wMfndr7WvpVeEiJ0JtXXQ%ykd;_oRTcA7R;cUQ(SIo#VUaWKC9!r z%*zGElz<4}5L;&e5`127;))?WrC11_=sR1Jgd!_gvhAK+dL65*;-zp(J zn~X32s|JnIX}+#Xltpt8lXA39RsEbLwe(2-b6ymDBLybBOmnGPyp*>W;xL}^*Gwt8 ze-KJugRpA6x(Y?){)YEQB8#D5#`cXvWwco*>3k3cmFt&Hsz-C=LUQ z0>!g;T^qww9^ZbMQOgkzioE{`||k<_51wda@We1aT3A#M*|bB z-PoNv!JwyNu{ps8(R&N=u$lv8!0)#8T{DU>3!+FGc<`dAk|@=QT;!;%nsPamOG_@8 zc3O3c`q6?|PKY`WTFx|GTjMVM@L6m?eHUF!o(igm$0 Rlyrm~^GENs4xTjR`)@Rn%gz7* literal 0 HcmV?d00001 diff --git a/lsh/__pycache__/hmac_lsh.cpython-39.pyc b/lsh/__pycache__/hmac_lsh.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69e38eff5c162126c3c53221106f48d5fbdfeb03 GIT binary patch literal 3124 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 literal 0 HcmV?d00001 diff --git a/lsh/__pycache__/lsh512.cpython-39.pyc b/lsh/__pycache__/lsh512.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..229a24000744c13bfe169a257574a7c32d3de35d GIT binary patch literal 7678 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 INu-kh1?`+cO8@`> literal 0 HcmV?d00001 diff --git a/lsh/__pycache__/lsh_digest.cpython-39.pyc b/lsh/__pycache__/lsh_digest.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f8cf583fe4e807721e41a590f2d7a7e507aefe4 GIT binary patch literal 2090 zcmZuy&u<$=6y9C`Nb0%>A(az|ry!wNvP>JP6;udyV{hUW&e~e9n)o-vu--K@ zyNy!07g8Yv+@KPN9$JY5+`00P_=Y$|q({#1&Du#*#3(bF_vX!e-}lXU*1dMEq@eY; z{`hQ!*`Lz5+;TAPqKg^^UpZ8K)z?CGsvT;ooae&aG=G>^m0iWp`-R7fUpUbY3x2Vy zRMy;2^NLcUdOSPk!FZC;rdOe@8#iy0)wOBUa+4sA+>pA=OL>qSlg$Kkxi=v*5=oGx z39FQ-L>zCBiI9)`3RAu#!qh z&(S2FzRCtdm1vam2xTD-V*D7=WmP|CULrjb|0oW__(&4;;>ZsqYjH=Oo`Xeq7(ZYc zqvb`2;sk&*;j^R2fGkgVwIn7k7!27;I8=trC6dl_Qec@5MS^P(;2dT#&uadcY%Rmk zG^pF&arSlFAhSyyyM51W7!BIcyRbK^v~N1icF!S1*t+E$PwrEntwxoM`yJcp zcByRxgSppfnFhS3Rd4kgrnO7k*lD#LYMFbcgS1YY9F$xoGY#zAp*_Q{H<4T4He051 zP(`X8)3IdU9VFJNquY*I@3nNBIz7A7?izs5Kw`_Zc5Ku!_6*CZp)P!6+=D^grrv60 z#Y&|2z`@Q?*4v!}+uUtB)NHpJ2Atal(COPPV_6K;>Mh;et5QSX(|098o7w;^8Op#F z#{H&|IZ;!`U)?d=mLyYeTaFF03a++uv3cL@8dcJ5vnyrUvD+w6A}K8PpaRlhr)4bj zNYOLw7iWVIDO?ZKFYq)By@hNzC=0pd@_BfzbgJTAs;G-%tJ~bVb$d0tb#u#u|CLsN zbr)T1VqnUliq}kIIe*>P{M=*pL_N&Iy5bl8wahB`D&DZt;tDc1f-!!}mZxFsd6RS5 z>*(SG40B{m6<(ezkJLHd#xFGfRs!z{^1#+$R}*zv@bSu_k1u_)mM zTFrk|_!UI|EhX{p^m_s?V}CE^V>jwI;-e^xU0?KHJo)YHyQeRn{BriqPXx|`v#-BD z`}XIv-+%n;ho_qlZq{zpwkrKlO!^{DxySmmV?1+F=7D$MXByRJ$BV0DmdFR(jXd@) zYKqq|DB8N3M_-pc_wf3KsqBJE7-_b|CEb)r0j+;jE0_NXu~ zJk#ddJU7oj&p}w#f?D_lDf}viMPU@+Yf~xmHR)QHuDtIi?xGlvMuH`WMF=)dCW{>S zWRfih`Gs5*m;64&T*$3UK4tac>o1usLuA>!W$`kC_%#fb;$nR;h;V*`!J;%6;7LzI zIWG?eU#4!jS}8*@yoxyGN$h8c`5Q8YJiAMSM3a$v8e^`bSH6AIl3Sr1JSU%W66IFB_Ie@&Et; literal 0 HcmV?d00001 diff --git a/lsh/__pycache__/lsh_template.cpython-39.pyc b/lsh/__pycache__/lsh_template.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3f08ea8cde327b364fc115e08f2ecddbd9bad56 GIT binary patch literal 6264 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 avN1BDoL_#AF|P&-cul9NNmpaq)c+6Wz+E2z literal 0 HcmV?d00001 diff --git a/lsh/hmac_lsh.py b/lsh/hmac_lsh.py new file mode 100644 index 0000000..984ef65 --- /dev/null +++ b/lsh/hmac_lsh.py @@ -0,0 +1,115 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +from .lsh_digest import LSHDigest + +## HMAC 구현 클래스 +class HmacLSH: + + __IPAD = 0x36 + __OPAD = 0x5c + + #__md = None + __blocksize = 0 + __i_key_pad = None + __o_key_pad = None + __outlenbits = 0 + + + ## 생성자 + # @param [in] self 객체 포인터 + # @param [in] wordlenbits 워드 길이 256, 512 중 하나여야 함 + # @param [in] outlenbits 출력 해시 길이 1 ~ wordlenbits 사이의 값이어야 함 + def __init__(self, wordlenbits, outlenbits = 0): + self.__outlenbits = outlenbits + if outlenbits > 0: + self.__md = LSHDigest.getInstance(wordlenbits, outlenbits) + else: + self.__md = LSHDigest.getInstance(wordlenbits) + self.__blocksize = self.__md.get_blocksize() + + ## HMAC 계산을 위한 초기화 + # @param [in] self 객체 포인터 + # @param [in] key 키 + def init(self, key): + + if key is None: + key = bytearray([0] * self._blocksize) + + if len(key) > self.__blocksize: + self.__md.reset() + key = self.__md.final(key) + + self.__i_key_pad = [HmacLSH.__IPAD] * self.__blocksize + self.__o_key_pad = [HmacLSH.__OPAD] * self.__blocksize + + for idx in range(len(key)): + self.__i_key_pad[idx] ^= key[idx] + self.__o_key_pad[idx] ^= key[idx] + + self.reset() + + + ## 새로운 HMAC을 계산할 수 있도록 객체를 초기화한다 + # @param [in] self 객체 포인터 + def reset(self): + self.__md.reset() + self.__md.update(self.__i_key_pad) + + + ## HMAC을 계산할 메시지를 추가한다. + # @param [in] self 객체 포인터 + # @param [in] msg 입력 메시지 + def update(self, msg): + if msg is None: + return + self.__md.update(msg) + + + ## HMAC을 계산하고 결과를 리턴한다. + # @param [in] self 객체 포인터 + # @return 계산된 HMAC 값 + def final(self): + result = self.__md.final() + self.__md.update(self.__o_key_pad) + self.__md.update(result) + result = self.__md.final() + self.reset() + return result + + ## digest 함수 - 최종 해쉬값을 계산하여 리턴한다. + # @param [in] wordlenbits 워드 길이 256, 512 중 하나여야 함 + # @param [in] outlenbits 출력 해시 길이 1 ~ wordlenbits 사이의 값이어야 함 + # @param [in] key HMAC key + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 오프셋 (바이트) + # @param [in] length 데이터 길이 (비트) + # @return 계산된 HMAC값 + @staticmethod + def digest(wordlenbits, outlenbits = None, key = None, data = None, offset = 0, length = -1): + hmac = HmacLSH(wordlenbits, outlenbits) + hmac.init(key) + hmac.update(data, offset, length) + return hmac.final() + diff --git a/lsh/lsh256.py b/lsh/lsh256.py new file mode 100644 index 0000000..2b4c58a --- /dev/null +++ b/lsh/lsh256.py @@ -0,0 +1,139 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +from .lsh_template import LSHTemplate + +MASK_U32 = 0xffffffff + +## LSH256 구현 클래스 +class LSH256(LSHTemplate): + + _MASK = MASK_U32 + _BLOCKSIZE = 128 + _NUMSTEP = 26 + _FORMAT_IN = ' 256: + raise ValueError("outlenbits should be 0 ~ 256") + + self._outlenbits = outlenbits + if self._outlenbits == 224: + self._cv = self.__IV224[:] + elif self._outlenbits == 256: + self._cv = self.__IV256[:] + else: + generate_iv() + + + ## 32비트 회전 연산 + # @param [in] value 회전하고자 하는 값 + # @param [in] rot 회전량 (비트) + @staticmethod + def __rol32(value, rot): + return ((value << rot) | (value >> (32 - rot))) & MASK_U32 + + + ## 스텝 함수 + # @param [in] idx 스텝 인덱스 + # @param [in] alpha 회전값 알파 + # @param [in] beta 회전값 베타 + def _step(self, idx, alpha, beta): + vl = 0 + vr = 0 + for colidx in range(8): + vl = (self._cv[colidx ] ^ self._msg[16 * idx + colidx ]) & MASK_U32 + vr = (self._cv[colidx + 8] ^ self._msg[16 * idx + colidx + 8]) & MASK_U32 + vl = LSH256.__rol32((vl + vr) & MASK_U32, alpha) ^ self._STEP[8 * idx + colidx] + vr = LSH256.__rol32((vl + vr) & MASK_U32, beta) + self._tcv[colidx ] = (vl + vr) & MASK_U32 + self._tcv[colidx + 8] = LSH256.__rol32(vr, self._GAMMA[colidx]) + + self._word_permutation() + diff --git a/lsh/lsh512.py b/lsh/lsh512.py new file mode 100644 index 0000000..cc52361 --- /dev/null +++ b/lsh/lsh512.py @@ -0,0 +1,194 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +from .lsh_template import LSHTemplate + +## 64비트 마스크 +MASK_U64 = 0xffffffffffffffff + +## LSH512 구현 클래스 +class LSH512(LSHTemplate): + + _MASK = MASK_U64 + _BLOCKSIZE = 256 + _NUMSTEP = 28 + _FORMAT_IN = ' 512: + raise ValueError("outlenbits should be 0 ~ 512") + + self._outlenbits = outlenbits + if self._outlenbits == 224: + self._cv = self.__IV224[:] + elif self._outlenbits == 256: + self._cv = self.__IV256[:] + elif self._outlenbits == 384: + self._cv = self.__IV384[:] + elif self._outlenbits == 512: + self._cv = self.__IV512[:] + else: + generate_iv() + + ## 64비트 회전 연산 + # @param [in] value 회전하고자 하는 값 + # @param [in] rot 회전량 (비트) + @staticmethod + def __rol64(value, rot): + return ((value << rot) | (value >> (64 - rot))) & MASK_U64 + + + ## 스텝 함수 + # @param [in] idx 스텝 인덱스 + # @param [in] alpha 회전값 알파 + # @param [in] beta 회전값 베타 + def _step(self, idx, alpha, beta): + vl = 0 + vr = 0 + for colidx in range(8): + vl = (self._cv[colidx ] ^ self._msg[16 * idx + colidx ]) & MASK_U64 + vr = (self._cv[colidx + 8] ^ self._msg[16 * idx + colidx + 8]) & MASK_U64 + vl = LSH512.__rol64((vl + vr) & MASK_U64, alpha) ^ self._STEP[8 * idx + colidx] + vr = LSH512.__rol64((vl + vr) & MASK_U64, beta) + self._tcv[colidx ] = (vl + vr) & MASK_U64 + self._tcv[colidx + 8] = LSH512.__rol64(vr, self._GAMMA[colidx]) + + self._word_permutation() + + diff --git a/lsh/lsh_digest.py b/lsh/lsh_digest.py new file mode 100644 index 0000000..6a98e59 --- /dev/null +++ b/lsh/lsh_digest.py @@ -0,0 +1,63 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +from .lsh256 import LSH256 +from .lsh512 import LSH512 + +## 해쉬 함수 wrapper 클래스 +class LSHDigest: + + ## 파라미터에 맞는 LSH 알고리즘 객체 생성 + # @param [in] wordlenbits 워드 길이 (비트) 256, 512만 가능함 + # @param [in] outlenbits 출력 길이 (비트) 1 ~ 256 (LSH-256) 혹은 1 ~ 512 (LSH-512) 가 가능함 + # @return LSH 객체 + @staticmethod + def getInstance(wordlenbits, outlenbits = None): + if outlenbits is None: + outlenbits = wordlenbits + + if wordlenbits == 256: + return LSH256(outlenbits) + + elif wordlenbits == 512: + return LSH512(outlenbits) + + else: + raise ValueError("Unsupported algorithm parameter"); + + + ## digest 함수 - 최종 해쉬값을 계산하여 리턴한다. + # @param [in] wordlenbits 워드 길이 256, 512 중 하나여야 함 + # @param [in] outlenbits 출력 해시 길이 1 ~ wordlenbits 사이의 값이어야 함 + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 오프셋 (바이트) + # @param [in] length 데이터 길이 (비트) + # @return 계산된 해쉬값 + @staticmethod + def digest(wordlenbits, outlenbits = None, data = None, offset = 0, length = -1): + if outlenbits is None: + outlenbits = wordlenbits + + lsh = LSHDigest.getInstance(wordlenbits, outlenbits) + return lsh.final(data, offset, length) \ No newline at end of file diff --git a/lsh/lsh_template.py b/lsh/lsh_template.py new file mode 100644 index 0000000..6aef012 --- /dev/null +++ b/lsh/lsh_template.py @@ -0,0 +1,240 @@ +#-*- coding: utf-8 -*- + +''' + Copyright (c) 2016 NSR (National Security Research Institute) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +''' + +import struct + +## LSH 추상 클래스 +class LSHTemplate: + + _MASK = None + _WORDBITLEN = None + _NUMSTEP = None + _BLOCKSIZE = 0 + _FORMAT_IN = None + _FORMAT_OUT = None + + _outlenbits = 0 + _boff = None + _cv = None + _tcv = None + _msg = None + _buf = None + + _STEP = None + _ALPHA_EVEN = None + _ALPHA_ODD = None + _BETA_EVEN = None + _BETA_ODD = None + _GAMMA = None + + ## 생성자 + # @param [in] self 객체 포인터 + # @param [in] outlenbits 출력 길이 (비트) + def __init__(self, outlenbits): + self._init(outlenbits) + + + ## HMAC 계산에 사용하기 위해서 내부 블록 길이 리턴 + # @param [in] self 객체 포인터 + # @return 내부 블록 길이 + def get_blocksize(self): + return self._BLOCKSIZE + + + ## 메시지 확장 함수 + # @param [in] self 객체 포인터 + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 인덱스 + def _msg_expansion(self, data, offset): + block = bytearray(data[offset:offset + self._BLOCKSIZE]) + self._msg[0:32] = struct.unpack(self._FORMAT_IN, block[0:self._BLOCKSIZE]) + + for i in range(2, self._NUMSTEP + 1): + idx = 16 * i + self._msg[idx ] = (self._msg[idx - 16] + self._msg[idx - 29]) & self._MASK + self._msg[idx + 1] = (self._msg[idx - 15] + self._msg[idx - 30]) & self._MASK + self._msg[idx + 2] = (self._msg[idx - 14] + self._msg[idx - 32]) & self._MASK + self._msg[idx + 3] = (self._msg[idx - 13] + self._msg[idx - 31]) & self._MASK + self._msg[idx + 4] = (self._msg[idx - 12] + self._msg[idx - 25]) & self._MASK + self._msg[idx + 5] = (self._msg[idx - 11] + self._msg[idx - 28]) & self._MASK + self._msg[idx + 6] = (self._msg[idx - 10] + self._msg[idx - 27]) & self._MASK + self._msg[idx + 7] = (self._msg[idx - 9] + self._msg[idx - 26]) & self._MASK + self._msg[idx + 8] = (self._msg[idx - 8] + self._msg[idx - 21]) & self._MASK + self._msg[idx + 9] = (self._msg[idx - 7] + self._msg[idx - 22]) & self._MASK + self._msg[idx + 10] = (self._msg[idx - 6] + self._msg[idx - 24]) & self._MASK + self._msg[idx + 11] = (self._msg[idx - 5] + self._msg[idx - 23]) & self._MASK + self._msg[idx + 12] = (self._msg[idx - 4] + self._msg[idx - 17]) & self._MASK + self._msg[idx + 13] = (self._msg[idx - 3] + self._msg[idx - 20]) & self._MASK + self._msg[idx + 14] = (self._msg[idx - 2] + self._msg[idx - 19]) & self._MASK + self._msg[idx + 15] = (self._msg[idx - 1] + self._msg[idx - 18]) & self._MASK + + ## 워드 단위 순환 함수 + # @param [in] self 객체 포인터 + def _word_permutation(self): + self._cv[ 0] = self._tcv[ 6] + self._cv[ 1] = self._tcv[ 4] + self._cv[ 2] = self._tcv[ 5] + self._cv[ 3] = self._tcv[ 7] + self._cv[ 4] = self._tcv[12] + self._cv[ 5] = self._tcv[15] + self._cv[ 6] = self._tcv[14] + self._cv[ 7] = self._tcv[13] + self._cv[ 8] = self._tcv[ 2] + self._cv[ 9] = self._tcv[ 0] + self._cv[10] = self._tcv[ 1] + self._cv[11] = self._tcv[ 3] + self._cv[12] = self._tcv[ 8] + self._cv[13] = self._tcv[11] + self._cv[14] = self._tcv[10] + self._cv[15] = self._tcv[ 9] + + ## 스텝 함수 - LSH를 상속받는 클래스에서 별도로 구현해야 함 + # @param [in] self 객체 포인터 + # @param [in] idx 스텝 인덱스 + # @param [in] alpha 회전값 알파 + # @param [in] beta 회전값 베타 + def _step(self, idx, alpha, beta): + raise NotImplementedError("Implement this method") + + ## 압축 함수 + # @param [in] self 객체 포인터 + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 인덱스 + def _compress(self, data, offset = 0): + + self._msg_expansion(data, offset) + + for idx in range(int(self._NUMSTEP / 2)): + self._step(2 * idx, self._ALPHA_EVEN, self._BETA_EVEN) + self._step(2 * idx + 1, self._ALPHA_ODD, self._BETA_ODD) + + for idx in range(16): + self._cv[idx] ^= self._msg[16 * self._NUMSTEP + idx] + + + ## IV 생성 함수 - LSH를 상속받는 클래스에서 별도로 구현해야 함 + # @param [in] self 객체 포인터 + # @param [in] outlenbits 출력 길이 (비트) + def _init_iv(self, outlenbits): + raise NotImplementedError("Implement this method") + + def _init(self, outlenbits): + self._boff = 0 + self._tcv = [0] * 16 + self._msg = [0] * (16 * (self._NUMSTEP + 1)) + self._buf = [0] * self._BLOCKSIZE + self._init_iv(outlenbits) + + ## 리셋 함수 - 키 입력 직후의 상태로 되돌린다 + # @param self 객체 포인터 + def reset(self): + self._init(self._outlenbits) + + + ## 업데이트 함수 + # @param [in] self 객체 포인터 + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 오프셋 (바이트) + # @param [in] length 데이터 길이 (비트) + def update(self, data, offset = 0, length = -1): + if data is None or len(data) == 0 or length == 0: + return + + if length == -1: + length = (len(data) - offset) << 3 + + len_bytes = length >> 3 + len_bits = length & 0x7 + + buf_idx = self._boff >> 3 + + if (self._boff & 0x7) > 0: + raise AssertionError("bit level update is not allowed") + + gap = self._BLOCKSIZE - (self._boff >> 3) + + if len_bytes >= gap: + self._buf[buf_idx:self._BLOCKSIZE] = data[offset:offset + gap] + self._compress(self._buf) + self._boff = 0 + offset += gap + len_bytes -= gap + + while len_bytes >= self._BLOCKSIZE: + self._compress(data, offset) + offset += self._BLOCKSIZE + len_bytes -= self._BLOCKSIZE + + if len_bytes > 0: + buf_idx = self._boff >> 3 + self._buf[buf_idx:buf_idx + len_bytes] = data[offset:offset + len_bytes] + self._boff += len_bytes << 3 + offset += len_bytes + + if len_bits > 0: + buf_idx = self._boff >> 3 + self._buf[buf_idx] = data[offset] & ((0xff >> len_bits) ^ 0xff) + self._boff += len_bits + + ## 종료 함수 - 최종 해쉬 값을 계산하여 리턴한다 + # @param [in] self 객체 포인터 + # @param [in] data 입력 데이터 + # @param [in] offset 데이터 시작 오프셋 (바이트) + # @param [in] length 데이터 길이 (비트) + # @return 계산된 해쉬값 + def final(self, data = None, offset = 0, length = -1): + if data is not None: + self.update(data, offset, length) + + rbytes = self._boff >> 3 + rbits = self._boff & 0x7 + + if rbits > 0: + self._buf[rbytes] |= (0x1 << (7 - rbits)) + else: + self._buf[rbytes] = 0x80 + + pos = rbytes + 1 + if (pos < self._BLOCKSIZE): + self._buf[pos:] = [0] * (self._BLOCKSIZE - pos) + + self._compress(self._buf) + + temp = [0] * 8 + for idx in range(8): + temp[idx] = (self._cv[idx] ^ self._cv[idx + 8]) & self._MASK + + self._init(self._outlenbits) + + rbytes = self._outlenbits >> 3 + rbits = self._outlenbits & 0x7 + if rbits > 0: + rbytes += 1 + + result = bytearray(struct.pack(self._FORMAT_OUT, temp[0], temp[1], temp[2], temp[3], temp[4], temp[5], temp[6], temp[7])) + result = result[0:rbytes] + if rbits > 0: + result[rbytes - 1] &= (0xff << (8 - rbits)) + + return result diff --git a/main.py b/main.py new file mode 100644 index 0000000..e69de29 diff --git a/main.spc b/main.spc new file mode 100644 index 0000000..d18b9c4 --- /dev/null +++ b/main.spc @@ -0,0 +1,2 @@ +PUSH 1 +SHOW \ No newline at end of file diff --git a/stackp.cpp b/stackp.cpp new file mode 100644 index 0000000..df32d6e --- /dev/null +++ b/stackp.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +using namespace std; + +void printStack(stack Stack){ + stack tmp; + while(!Stack.empty()){ + tmp.push(Stack.top()); + cout << Stack.top() << ' '; + Stack.pop(); + } + while(!tmp.empty()){ + Stack.push(tmp.top()); + tmp.pop(); + } + cout << endl; +} + +int main(int argc, char** argv ){ + if(argc<2){ + cout << "Argument Error"; + return(0); + } + FILE* fp = fopen(argv[1],"rt"); + if(fp == NULL){ + cout << "File Error."; + return(1); + } + char str[64]; + char* stok; + int op = 0; + int arg = 0; + int loop = 0; + + stack S; + int tmp1, tmp2; + + while(!feof(fp)){ + loop++; + fgets(str, 64, fp); + stok = strtok(str,"\n"); + stok = strtok(str," "); + + op = 0; + arg = 0; + if(!strcmp(stok,"PUSH")) op = 1; + else if(!strcmp(stok,"POP")) op = 2; + else if(!strcmp(stok,"ADD")) op = 3; + else if(!strcmp(stok,"SUB")) op = 4; + else if(!strcmp(stok,"MUL")) op = 5; + else if(!strcmp(stok,"EQUAL")) op = 6; + else if(!strcmp(stok,"SHOW")) op = 7; + + if(op==1||op==2){ + stok = strtok(NULL," "); + arg = atoi(stok); + } + + switch(op){ + case 1: + S.push(arg); + break; + case 2: + S.pop(); + break; + case 3: + tmp1 = S.top(); + S.pop(); + tmp2 = S.top(); + S.pop(); + S.push(tmp1 + tmp2); + break; + case 4: + tmp1 = S.top(); + S.pop(); + tmp2 = S.top(); + S.pop(); + S.push(tmp1 - tmp2); + break; + case 5: + tmp1 = S.top(); + S.pop(); + tmp2 = S.top(); + S.pop(); + S.push(tmp1 * tmp2); + break; + case 6: + tmp1 = S.top(); + S.pop(); + tmp2 = S.top(); + S.pop(); + S.push((tmp1 == tmp2)); + break; + case 7: + cout << S.top() << endl; + break; + } + + // printStack(S); + } +} \ No newline at end of file diff --git a/stackp.exe b/stackp.exe new file mode 100644 index 0000000000000000000000000000000000000000..e01e1e405788fa83f851e23ec02671bc1671dfef GIT binary patch literal 96855 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 E54a^zEC2ui literal 0 HcmV?d00001