python - CloudFront के माध्यम से निजी सामग्री तक पहुंचने के लिए हस्ताक्षरित यूआरएल उत्पन्न करने के प्रदर्शन के साथ कैसे सामना करें?




amazon-web-services web-applications (2)

हस्ताक्षरित कुकीज़ का उपयोग करें

जब मैं कई निजी यूआरएल के साथ CloudFront का उपयोग करता हूं, तो जब सभी प्रतिबंध मिलते हैं तो मैं हस्ताक्षरित कुकीज़ का उपयोग करना पसंद करता हूं। यह हस्ताक्षरित कुकीज़ की पीढ़ी को तेज नहीं करता है, लेकिन यह हस्ताक्षरित अनुरोधों की संख्या को कम करता है, जब तक वे उपयोगकर्ता की एकमात्र अवधि समाप्त नहीं हो जाती।

आरएसए हस्ताक्षर जनरेशन ट्यूनिंग

मैं सोच सकता हूं कि आपके पास ऐसी आवश्यकताएं हो सकती हैं जो हस्ताक्षरित कुकीज़ को एक अमान्य विकल्प के रूप में प्रदान करते हैं। उस मामले में मैंने बीटीओ और क्रिप्टोग्राफी के साथ इस्तेमाल किए गए आरएसए मॉड्यूल की तुलना करके हस्ताक्षर को गति देने की कोशिश की। दो अतिरिक्त वैकल्पिक विकल्प m2crypto और pycrypto हैं लेकिन इस उदाहरण के लिए मैं क्रिप्टोग्राफी का उपयोग करेगा

अलग-अलग मॉड्यूल वाले यूआरएल हस्ताक्षरित करने के प्रदर्शन की जांच करने के लिए मैंने स्ट्रिंग पर हस्ताक्षर किए बिना किसी भी तर्क को हटाने के लिए विधि _sign_string को घटा दिया और फिर एक नया Distribution वर्ग बनाया। फिर मैंने निजी कुंजी और उदाहरण के URL को boto परीक्षण से परीक्षण करने के लिए लिया।

परिणाम बताते हैं कि क्रिप्टोग्राफी तेज है, लेकिन फिर भी हस्ताक्षर करने के अनुरोध के करीब 1ms की आवश्यकता है। इन परिणामों को iPython द्वारा समय में स्कैड वेरिएबल्स के उपयोग से अधिक ऊंचा हो गया है।

timeit -n10000 rsa_distribution.create_signed_url(url, message, expire_time)
10000 loops, best of 3: 6.01 ms per loop

timeit -n10000 cryptography_distribution.create_signed_url(url, message, expire_time)
10000 loops, best of 3: 644 µs per loop

पूरी स्क्रिप्ट:

from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes

import rsa

from boto.cloudfront.distribution import Distribution

from textwrap import dedent

# The private key provided in the Boto tests
pk_key = dedent("""
    -----BEGIN RSA PRIVATE KEY-----
    MIICXQIBAAKBgQDA7ki9gI/lRygIoOjV1yymgx6FYFlzJ+z1ATMaLo57nL57AavW
    hb68HYY8EA0GJU9xQdMVaHBogF3eiCWYXSUZCWM/+M5+ZcdQraRRScucmn6g4EvY
    2K4W2pxbqH8vmUikPxir41EeBPLjMOzKvbzzQy9e/zzIQVREKSp/7y1mywIDAQAB
    AoGABc7mp7XYHynuPZxChjWNJZIq+A73gm0ASDv6At7F8Vi9r0xUlQe/v0AQS3yc
    N8QlyR4XMbzMLYk3yjxFDXo4ZKQtOGzLGteCU2srANiLv26/imXA8FVidZftTAtL
    viWQZBVPTeYIA69ATUYPEq0a5u5wjGyUOij9OWyuy01mbPkCQQDluYoNpPOekQ0Z
    WrPgJ5rxc8f6zG37ZVoDBiexqtVShIF5W3xYuWhW5kYb0hliYfkq15cS7t9m95h3
    1QJf/xI/AkEA1v9l/WN1a1N3rOK4VGoCokx7kR2SyTMSbZgF9IWJNOugR/WZw7HT
    njipO3c9dy1Ms9pUKwUF46d7049ck8HwdQJARgrSKuLWXMyBH+/l1Dx/I4tXuAJI
    rlPyo+VmiOc7b5NzHptkSHEPfR9s1OK0VqjknclqCJ3Ig86OMEtEFBzjZQJBAKYz
    470hcPkaGk7tKYAgP48FvxRsnzeooptURW5E+M+PQ2W9iDPPOX9739+Xi02hGEWF
    B0IGbQoTRFdE4VVcPK0CQQCeS84lODlC0Y2BZv2JxW3Osv/WkUQ4dslfAQl1T303
    7uwwr7XTroMv8dIFQIPreoPhRKmd/SbJzbiKfS/4QDhU
    -----END RSA PRIVATE KEY-----""")

# Initializing keys in a global context
cryptography_private_key = serialization.load_pem_private_key(
    pk_key,
    password=None,
    backend=default_backend())


# Instantiate a signer object using PKCS 1v 15, this is not recommended but required for Amazon
def sign_with_cryptography(message):
    signer = cryptography_private_key.signer(
        padding.PKCS1v15(),
        hashes.SHA1())

    signer.update(message)
    return signer.finalize()


# Initializing the key in a global context
rsa_private_key = rsa.PrivateKey.load_pkcs1(pk_key)


def sign_with_rsa(message):
    signature = rsa.sign(str(message), rsa_private_key, 'SHA-1')

    return signature


# All this information comes from the Boto tests.
url = "http://d604721fxaaqy9.cloudfront.net/horizon.jpg?large=yes&license=yes"
expected_url = "http://d604721fxaaqy9.cloudfront.net/horizon.jpg?large=yes&license=yes&Expires=1258237200&Signature=Nql641NHEUkUaXQHZINK1FZ~SYeUSoBJMxjdgqrzIdzV2gyEXPDNv0pYdWJkflDKJ3xIu7lbwRpSkG98NBlgPi4ZJpRRnVX4kXAJK6tdNx6FucDB7OVqzcxkxHsGFd8VCG1BkC-Afh9~lOCMIYHIaiOB6~5jt9w2EOwi6sIIqrg_&Key-Pair-Id=PK123456789754"
message = "PK123456789754"
expire_time = 1258237200


class CryptographyDistribution(Distribution):
    def _sign_string(
            self,
            message,
            private_key_file=None,
            private_key_string=None):
        return sign_with_cryptography(message)


class RSADistribution(Distribution):
    def _sign_string(
            self,
            message,
            private_key_file=None,
            private_key_string=None):
        return sign_with_rsa(message)


cryptography_distribution = CryptographyDistribution()
rsa_distribution = RSADistribution()

cryptography_url = cryptography_distribution.create_signed_url(
    url,
    message,
    expire_time)

rsa_url = rsa_distribution.create_signed_url(
    url,
    message,
    expire_time)

assert cryptography_url == rsa_url == expected_url, "URLs do not match"

निष्कर्ष

यद्यपि क्रिप्टोग्राफी मॉड्यूल इस परीक्षण में बेहतर प्रदर्शन करता है, लेकिन मैं सुझाव देता हूं कि हस्ताक्षरित कुकीज़ का उपयोग करने का एक तरीका खोजना है लेकिन मुझे आशा है कि यह जानकारी उपयोगी है।

एडब्ल्यूएस एस 3 और क्लाउडफ्रंट का एक आम उपयोग मामला निजी सामग्री की सेवा कर रहा है। सामान्य समाधान S3 का उपयोग करके निजी फाइलों को एक्सेस करने के लिए हस्ताक्षरित CloudFront यूआरएल का उपयोग कर रहा है।

हालांकि, इन यूआरएल की पीढ़ी एक लागत के साथ आती है: निजी कुंजी का उपयोग करते हुए दिए गए यूआरएल के आरएसए हस्ताक्षर की गणना करना। पायथन (या boto , एडब्ल्यूएस के पायथन एसडीके) के लिए, rsa ( https://pypi.python.org/pypi/rsa ) पुस्तकालय इस कार्य के लिए प्रयोग किया जाता है। मेरी देर से 2014 एमबीपी पर, 2048-बिट कुंजी के साथ प्रति कम्पन की ~ 25 एम के बारे में लगता है

यह लागत संभावित रूप से किसी ऐसे एप्लिकेशन की स्केलेबिलिटी पर प्रभाव डालती है जो क्लाउडफ़्रन्ट द्वारा निजी सामग्री तक पहुंच के लिए अधिकृत करने के लिए इस दृष्टिकोण का उपयोग करती है। एकाधिक क्लाइंटों की कल्पना करें कि कई फाइलों तक पहुंच 25 से 30ms / req पर है

यह मुझे लगता है कि हस्ताक्षर गणना में ही ज्यादा सुधार नहीं किया जा सकता है, हालांकि ऊपर वर्णित rsa पुस्तकालय लगभग 1.5 साल पहले अंतिम अद्यतन हुआ था। मुझे आश्चर्य है कि क्या अन्य तकनीकें या डिजाइन हैं जो उच्च स्केलेबिलिटी हासिल करने के लिए इस प्रक्रिया के प्रदर्शन को अनुकूलित कर सकते हैं। या क्या हमें बस और अधिक हार्डवेयर फेंकना पड़ता है और इसे एक बाध्य बल में हल करने की कोशिश करनी है?

एक अनुकूलन एपीआई समापन बिंदु प्रति अनुरोध एकाधिक फ़ाइल हस्ताक्षर स्वीकार कर सकता है और अलग-अलग अनुरोधों में व्यक्तिगत रूप से उनसे निपटने के बजाय हस्ताक्षरित यूआरएल को वापस लौट सकता है, लेकिन उन सभी हस्ताक्षरों के लिए आवश्यक कुल समय अभी भी वहां मौजूद है।


संक्षिप्त

विचार करें कि क्या आप (उपयोग कर सकते हैं python-cryptography , @ एरिक-ई) आपके उपयोग केस के विवरण के अनुसार, कम की लंबाई (और संभवतः चाबी को अधिक बार बदलें ) का उपयोग करें। जबकि मैं ~ 1550μ में उत्पन्न 2048-बिट कुंजी एडब्ल्यूएस के साथ साइन इन कर सकता हूं, यह केवल 1028 बिट्स पर ~ 307μ, ~ 744 बिट्स पर 184 एमएल और 512 बीट्स पर ~ 113μ एस लेता है।

व्याख्या

थोड़ी देर के लिए इस पर गौर करने के बाद, मैं एक और दिशा में जा रहा हूँ और (पहले से ही महान) उत्तर @ एरिक-ए ने दिया इससे पहले कि मैं इसमें शामिल होने का उल्लेख करना चाहिए, मुझे नहीं पता कि यह विचार कितना स्वीकार्य है; मैं अभी प्रदर्शन के प्रभाव पर रिपोर्ट कर रहा हूं (इस बारे में पूछे जाने वाले प्रश्न के लिए पोस्ट के अंत को देखें)।

मैं cryptography साथ हस्ताक्षर करने पर समय एकत्र कर रहा हूं @ एरिक-ई ने सुझाव दिया है, और इसके कारण अभी भी बड़ी प्रदर्शन खाड़ी और एस 3 के लिए हमारी मौजूदा हस्ताक्षर पद्धति के कारण, मैंने यह देखने के लिए कोड का प्रोफ़ाइल करने का निर्णय लिया कि क्या ऐसा लगता है कि कुछ स्पष्ट हो सकता है चबाने का समय:

>>> cProfile.runctx('[sign_url_cloudfront2("...") for x in range(0,100)]', globals(), locals(), sort="time")
         9403 function calls in 0.218 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      200    0.161    0.001    0.161    0.001 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_sign}
      100    0.006    0.000    0.186    0.002 rsa.py:214(_finalize_pkey_ctx)
     1200    0.004    0.000    0.008    0.000 {isinstance}
      400    0.004    0.000    0.007    0.000 api.py:212(new)
      100    0.003    0.000    0.218    0.002 views.py:888(sign_url_cloudfront2)
      300    0.002    0.000    0.004    0.000 abc.py:128(__instancecheck__)
      100    0.002    0.000    0.008    0.000 hashes.py:53(finalize)
      200    0.002    0.000    0.005    0.000 gc_weakref.py:10(build)
      100    0.002    0.000    0.007    0.000 hashes.py:15(__init__)
      100    0.002    0.000    0.018    0.000 rsa.py:151(__init__)
      100    0.002    0.000    0.014    0.000 hashes.py:68(__init__)
      200    0.002    0.000    0.003    0.000 gc_weakref.py:14(remove)
      200    0.002    0.000    0.003    0.000 api.py:239(cast)
      100    0.002    0.000    0.190    0.002 rsa.py:207(finalize)
      200    0.001    0.000    0.007    0.000 api.py:325(gc)
      500    0.001    0.000    0.001    0.000 {getattr}
      400    0.001    0.000    0.001    0.000 {_cffi_backend.newp}
      400    0.001    0.000    0.001    0.000 api.py:150(_typeof)
      200    0.001    0.000    0.002    0.000 api.py:266(buffer)
      200    0.001    0.000    0.001    0.000 utils.py:18(<lambda>)
      300    0.001    0.000    0.001    0.000 _weakrefset.py:68(__contains__)
      200    0.001    0.000    0.001    0.000 {_cffi_backend.buffer}
      100    0.001    0.000    0.002    0.000 hashes.py:49(update)
      100    0.001    0.000    0.010    0.000 hashes.py:102(finalize)
      100    0.001    0.000    0.003    0.000 hashes.py:88(update)
      200    0.001    0.000    0.001    0.000 {method 'encode' of 'str' objects}
      100    0.001    0.000    0.019    0.000 rsa.py:528(signer)
      300    0.001    0.000    0.001    0.000 {len}
      100    0.001    0.000    0.001    0.000 base64.py:42(b64encode)
      100    0.001    0.000    0.008    0.000 backend.py:148(create_hash_ctx)
      200    0.001    0.000    0.001    0.000 {_cffi_backend.cast}
      200    0.001    0.000    0.001    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_get_digestbyname}
      100    0.001    0.000    0.001    0.000 {method 'format' of 'str' objects}
      100    0.001    0.000    0.003    0.000 rsa.py:204(update)
      200    0.000    0.000    0.000    0.000 {method 'pop' of 'dict' objects}
      100    0.000    0.000    0.000    0.000 {binascii.b2a_base64}
      200    0.000    0.000    0.000    0.000 {_cffi_backend.typeof}
      100    0.000    0.000    0.000    0.000 {time.time}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestUpdate}
        1    0.000    0.000    0.218    0.218 <string>:1(<module>)
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestInit_ex}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_new}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_free}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestFinal_ex}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_create}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_set_rsa_padding}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_size}
      100    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_cleanup}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_set_signature_md}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_sign_init}
      100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_destroy}
        1    0.000    0.000    0.000    0.000 {range}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

जबकि साइन-इन के अंदर छिपी कुछ छोटी बचत हो सकती है, वहीं अधिकांश समय अंतिम कॉल () कॉल के अंदर बिताए जाते हैं, और लगभग सभी समय वास्तविक साइन कॉल के अंदर खुलते हैं। हालांकि यह थोड़ा निराशाजनक था, यह एक स्पष्ट संकेत था कि मुझे बचत के लिए वास्तविक हस्ताक्षर प्रक्रिया पर देखना चाहिए।

मैं सिर्फ हमारे लिए उत्पन्न 2048-बिट कुंजी बादलफ़्रंट का उपयोग कर रहा था, इसलिए मैंने यह देखने का फैसला किया कि प्रदर्शन पर एक छोटी सी कुंजी का क्या प्रभाव होगा। मैं कम कुंजी का उपयोग करके प्रोफ़ाइल को फिर से चलाया:

>>> cProfile.runctx('[sign_url_cloudfront2("...") for x in range(0,100)]', globals(), locals(), sort="time")
        9203 function calls in 0.063 seconds

  Ordered by: internal time

  ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     100    0.008    0.000    0.008    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_sign}
     400    0.005    0.000    0.008    0.000 api.py:212(new)
     100    0.004    0.000    0.033    0.000 rsa.py:214(_finalize_pkey_ctx)
    1200    0.004    0.000    0.008    0.000 {isinstance}
     100    0.003    0.000    0.063    0.001 views.py:897(sign_url_cloudfront2)
     300    0.002    0.000    0.004    0.000 abc.py:128(__instancecheck__)
     100    0.002    0.000    0.008    0.000 hashes.py:53(finalize)
     200    0.002    0.000    0.005    0.000 gc_weakref.py:10(build)
     100    0.002    0.000    0.007    0.000 hashes.py:15(__init__)
     100    0.002    0.000    0.014    0.000 hashes.py:68(__init__)
     100    0.002    0.000    0.018    0.000 rsa.py:151(__init__)
     200    0.002    0.000    0.003    0.000 gc_weakref.py:14(remove)
     100    0.001    0.000    0.036    0.000 rsa.py:207(finalize)
     200    0.001    0.000    0.003    0.000 api.py:239(cast)
     200    0.001    0.000    0.006    0.000 api.py:325(gc)
     500    0.001    0.000    0.001    0.000 {getattr}
     200    0.001    0.000    0.002    0.000 api.py:266(buffer)
     400    0.001    0.000    0.001    0.000 {_cffi_backend.newp}
     400    0.001    0.000    0.001    0.000 api.py:150(_typeof)
     100    0.001    0.000    0.010    0.000 hashes.py:102(finalize)
     200    0.001    0.000    0.002    0.000 utils.py:18(<lambda>)
     300    0.001    0.000    0.001    0.000 _weakrefset.py:68(__contains__)
     100    0.001    0.000    0.002    0.000 hashes.py:88(update)
     100    0.001    0.000    0.001    0.000 hashes.py:49(update)
     200    0.001    0.000    0.001    0.000 {method 'encode' of 'str' objects}
     200    0.001    0.000    0.001    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_get_digestbyname}
     100    0.001    0.000    0.001    0.000 base64.py:42(b64encode)
     100    0.001    0.000    0.008    0.000 backend.py:148(create_hash_ctx)
     100    0.001    0.000    0.019    0.000 rsa.py:520(signer)
     200    0.001    0.000    0.001    0.000 {_cffi_backend.buffer}
     200    0.001    0.000    0.001    0.000 {method 'pop' of 'dict' objects}
     200    0.001    0.000    0.001    0.000 {_cffi_backend.cast}
     100    0.001    0.000    0.001    0.000 {method 'format' of 'str' objects}
     100    0.001    0.000    0.001    0.000 {time.time}
     100    0.001    0.000    0.003    0.000 rsa.py:204(update)
     200    0.000    0.000    0.000    0.000 {len}
     200    0.000    0.000    0.000    0.000 {_cffi_backend.typeof}
     100    0.000    0.000    0.000    0.000 {binascii.b2a_base64}
     100    0.000    0.000    0.000    0.000 {method 'translate' of 'str' objects}
       1    0.000    0.000    0.063    0.063 <string>:1(<module>)
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestUpdate}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_new}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestInit_ex}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_destroy}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_DigestFinal_ex}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_create}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_sign_init}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_size}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_MD_CTX_cleanup}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_free}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_set_signature_md}
     100    0.000    0.000    0.000    0.000 {_Cryptography_cffi_a269d620xd5c405b7.EVP_PKEY_CTX_set_rsa_padding}
       1    0.000    0.000    0.000    0.000 {range}
       1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

जैसा कि एरिक-ई के उत्तर पर मेरी टिप्पणी में उल्लिखित है, cryptography मॉड्यूल के साथ 2048-बिट कुंजी का इस्तेमाल करते समय हमारी पूरी हस्ताक्षर विधि के लिए मैंने देखा कि रनटाइम ~ 1550 लाख था। 512-बीट कुंजी के साथ एक ही टेस्ट को दोहराते समय रनटाइम नीचे ~ 113μ के लिए लाया जाता है (हमारे एस 3 हस्ताक्षरित विधि के 30μs से एक पत्थर का फेंक)

यह परिणाम सार्थक लगता है, लेकिन यह आपके उद्देश्य के लिए एक छोटी कुंजी का उपयोग करने के लिए कितने स्वीकार्य पर निर्भर करता है मैं मार्च से एक मोज़िला इश्यू रिपोर्ट पर एक टिप्पणी प्राप्त करने में सक्षम था जिसका सुझाव है कि 512-बिट कुंजी को ईसी 2 पर 8 घंटे में 75 डॉलर में तोड़ा जा सकता है





amazon-cloudfront