title: Cloud storage and secure download strategy on app engine. GCS acl or blobstore
body: <p>My appengine app creates cloudstorage files. The files will be downloaded by a third party. The files contain <strong>personal medical information</strong>. </p>
  
  <p>What would be the preferred way of downloading:</p>
  
  <ol>
  <li>Using a direct GCS download link with a user READER acl. </li>
  <li>Or using a blobstore download handler in an appengine app.</li>
  </ol>
  
  <p>Both solutions require the third party to login (google login). Performance is not an issue. Privacy and the occurrence of security errors and mistakes are.</p>
  
  <p>Using an encrypted zip file to download is an option. This means I have to store the password in the project. Or e-mail a random password?</p>
  
  <p><strong>Update The appengine code I used to create a signed download url</strong></p>
  
  <pre><code>import time
  import urllib
  from datetime import datetime, timedelta
  from google.appengine.api import app_identity
  import os
  import base64
  
  API_ACCESS_ENDPOINT = 'https://storage.googleapis.com'
  
  # Use the default bucket in the cloud and not the local SDK one from app_identity
  default_bucket = '%s.appspot.com' % os.environ['APPLICATION_ID'].split('~', 1)[1]
  google_access_id = app_identity.get_service_account_name()
  
  
  def sign_url(bucket_object, expires_after_seconds=60):
      """ cloudstorage signed url to download cloudstorage object without login
          Docs : https://cloud.google.com/storage/docs/access-control?hl=bg#Signed-URLs
          API : https://cloud.google.com/storage/docs/reference-methods?hl=bg#getobject
      """
  
      method = 'GET'
      gcs_filename = '/%s/%s' % (default_bucket, bucket_object)
      content_md5, content_type = None, None
  
      expiration = datetime.utcnow() + timedelta(seconds=expires_after_seconds)
      expiration = int(time.mktime(expiration.timetuple()))
  
      # Generate the string to sign.
      signature_string = '\n'.join([
          method,
          content_md5 or '',
          content_type or '',
          str(expiration),
          gcs_filename])
  
      _, signature_bytes = app_identity.sign_blob(signature_string)
      signature = base64.b64encode(signature_bytes)
  
      # Set the right query parameters.
      query_params = {'GoogleAccessId': google_access_id,
                      'Expires': str(expiration),
                      'Signature': signature}
  
      # Return the download URL.
      return '{endpoint}{resource}?{querystring}'.format(endpoint=API_ACCESS_ENDPOINT,
                                                         resource=gcs_filename,
                                                         querystring=urllib.urlencode(query_params))
  </code></pre>