]>
git.frykholm.com Git - friends.git/blob - friends/magicsig/magicsigalg.py
9753c532f196720a81442515d3c4661f056121db
3 # Copyright 2009 Google Inc. All Rights Reserved.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Implementation of Magic Signatures low level operations.
19 See Magic Signatures RFC for specification. This implements
20 the cryptographic layer of the spec, essentially signing and
21 verifying byte buffers using a public key algorithm.
24 __author__
= 'jpanzer@google.com (John Panzer)'
30 # PyCrypto: Note that this is not available in the
31 # downloadable GAE SDK, must be installed separately.
32 # See http://code.google.com/p/googleappengine/issues/detail?id=2493
33 # for why this is most easily installed under the
34 # project's path rather than somewhere more sane.
35 import Crypto
.PublicKey
36 import Crypto
.PublicKey
.RSA
37 from Crypto
.Util
import number
42 # Note that PyCrypto is a very low level library and its documentation
43 # leaves something to be desired. As a cheat sheet, for the RSA
44 # algorithm, here's a decoding of terminology:
45 # n - modulus (public)
47 # d - private exponent
49 # (n, d) - private key
50 # (p, q) - the (private) primes from which the keypair is derived.
52 # Thus a public key is a tuple (n,e) and a public/private key pair
53 # is a tuple (n,e,d). Often the exponent is 65537 so for convenience
54 # we default e=65537 in this code.
57 def GenSampleSignature(text
):
58 """Demo using a hard coded, test public/private keypair."""
59 demo_keypair
= ('RSA.mVgY8RN6URBTstndvmUUPb4UZTdwvwmddSKE5z_jvKUEK6yk1'
60 'u3rrC9yN8k6FilGj9K0eeUPe2hf4Pj-5CmHww=='
62 '.Lgy_yL3hsLBngkFdDw1Jy9TmSRMiH6yihYetQ8jy-jZXdsZXd8V5'
63 'ub3kuBHHk4M39i3TduIkcrjcsiWQb77D8Q==')
65 signer
= SignatureAlgRsaSha256(demo_keypair
)
66 return signer
.Sign(text
)
71 """Turns a bignum into a urlsafe base64 encoded string."""
72 return base64
.urlsafe_b64encode(number
.long_to_bytes(num
))
76 """Turns a urlsafe base64 encoded string into a bignum."""
78 return number
.bytes_to_long(base64
.urlsafe_b64decode(b64
))
80 # Patterns for parsing serialized keys
81 _WHITESPACE_RE
= re
.compile(r
'\s+')
88 (?P<private_exp>[^\.]+)
93 # Implementation of the Magic Envelope signature algorithm
94 class SignatureAlgRsaSha256(object):
95 """Signature algorithm for RSA-SHA256 Magic Envelope."""
97 def __init__(self
, rsa_key
):
98 """Initializes algorithm with key information.
101 rsa_key: Key in either string form or a tuple in the
102 format expected by Crypto.PublicKey.RSA.
104 ValueError: The input format was incorrect.
106 if isinstance(rsa_key
, tuple):
107 self
.keypair
= Crypto
.PublicKey
.RSA
.construct(rsa_key
)
109 self
._InitFromString
(rsa_key
)
111 def ToString(self
, full_key_pair
=True):
112 """Serializes key to a safe string storage format.
115 full_key_pair: Whether to save the private key portion as well.
117 The string representation of the key in the format:
119 RSA.mod.exp[.optional_private_exp]
121 Each component is a urlsafe-base64 encoded representation of
122 the corresponding RSA key field.
124 mod
= _NumToB64(self
.keypair
.n
)
125 exp
= '.' + _NumToB64(self
.keypair
.e
)
127 if full_key_pair
and self
.keypair
.d
:
128 private_exp
= '.' + _NumToB64(self
.keypair
.d
)
129 return 'RSA.' + mod
+ exp
+ private_exp
131 def _InitFromString(self
, text
):
132 """Parses key from a standard string storage format.
135 text: The key in text form. See ToString for description
138 ValueError: The input format was incorrect.
140 # First, remove all whitespace:
141 text
= re
.sub(_WHITESPACE_RE
, '', text
)
142 #throw away first item and convert from base64 to long
143 items
= [_B64ToNum(item
) for item
in text
.split('.')[1:]]
144 self
.keypair
= Crypto
.PublicKey
.RSA
.construct(items
)
147 """Returns string identifier for algorithm used."""
150 def _MakeEmsaMessageSha256(self
, msg
, modulus_size
, logf
=None):
151 """Algorithm EMSA_PKCS1-v1_5 from PKCS 1 version 2.
153 This is derived from keyczar code, and implements the
154 additional ASN.1 compatible magic header bytes and
155 padding needed to implement PKCS1-v1_5.
158 msg: The message to sign.
159 modulus_size: The size of the key (in bits) used.
161 The byte sequence of the message to be signed.
163 magic_sha256_header
= [0x30, 0x31, 0x30, 0xd, 0x6, 0x9, 0x60, 0x86, 0x48,
164 0x1, 0x65, 0x3, 0x4, 0x2, 0x1, 0x5, 0x0, 0x4, 0x20]
166 hash_of_msg
= hashlib
.sha256(msg
).digest() #???
168 self
._Log
(logf
, 'sha256 digest of msg %s: %s' % (msg
, hash_of_msg
))
170 encoded
= bytes(magic_sha256_header
) + hash_of_msg
171 msg_size_bits
= modulus_size
+ 8-(modulus_size
% 8) # Round up to next byte
173 pad_string
= b
'\xFF' * (msg_size_bits
// 8 - len(encoded
) - 3)
174 return b
'\x00\x01' + pad_string
+ b
'\x00' + encoded
176 def _Log(self
, logf
, s
):
177 """Append message to log if log exists."""
182 def Sign(self
, bytes_to_sign
, logf
=None):
183 """Signs the bytes using PKCS-v1_5.
186 bytes_to_sign: The bytes to be signed.
188 The signature in base64url encoded format.
190 # Implements PKCS1-v1_5 w/SHA256 over the bytes, and returns
191 # the result as a base64url encoded bignum.
193 self
._Log
(logf
, 'bytes_to_sign = %s' % bytes_to_sign
)
194 self
._Log
(logf
, 'len = %d' % len(bytes_to_sign
))
196 self
._Log
(logf
, 'keypair size : %s' % self
.keypair
.size())
198 # Generate the PKCS1-v1_5 compatible message, which includes
199 # magic ASN.1 bytes and padding:
200 emsa_msg
= self
._MakeEmsaMessageSha
256(bytes_to_sign
, self
.keypair
.size(), logf
)
201 # TODO(jpanzer): Check whether we need to use max keysize above
202 # or just keypair.size
204 self
._Log
(logf
, 'emsa_msg = %s' % emsa_msg
)
206 # Compute the signature:
207 signature_long
= self
.keypair
.sign(emsa_msg
, None)[0]
209 # Encode the signature as armored text:
210 signature_bytes
= number
.long_to_bytes(signature_long
)
212 self
._Log
(logf
, 'signature_bytes = [%s]' % signature_bytes
)
214 return base64
.urlsafe_b64encode(signature_bytes
)
216 def Verify(self
, signed_bytes
, signature_b64
):
217 """Determines the validity of a signature over a signed buffer of bytes.
220 signed_bytes: string The buffer of bytes the signature_b64 covers.
221 signature_b64: string The putative signature, base64-encoded, to check.
223 True if the request validated, False otherwise.
225 # Generate the PKCS1-v1_5 compatible message, which includes
226 # magic ASN.1 bytes and padding:
227 emsa_msg
= self
._MakeEmsaMessageSha
256(signed_bytes
,
230 # Get putative signature:
231 putative_signature
= base64
.urlsafe_b64decode(signature_b64
)
232 putative_signature
= number
.bytes_to_long(putative_signature
)
234 # Verify signature given public key:
235 return self
.keypair
.verify(emsa_msg
, (putative_signature
,))