Telegram Passport Encryption Details

Telegram Passport data is stored encrypted End-to-End which means that the Telegram server does not have access to the data and only functions as a storage for encrypted data it can't decipher. Encryption and decryption are handled exclusively by the Telegram clients, which are open source.

Overview

To encrypt each particular element of Telegram Passport, the client generates a random secret. The secret is a 32-byte number with the modulo 255 sum of bytes equal to 239. This secret is in turn encrypted with the passport_secret that is generated when the user creates their Telegram Passport. passport_secret is encrypted with the user's password and is stored encrypted in the Telegram Cloud.

Passport Secret

The passport secret is one of the secret parameters used to encrypt the data uploaded by the user to the Telegram Cloud.

When first setting up Telegram Passport it must be created, encrypted and uploaded as described in Passport Secret Encryption.

When using Telegram Passport normally, it must be downloaded and decrypted for use as described in Passport Secret Decryption.

The passport secret must also be downloaded, re-encrypted and re-uploaded as described in Passport Secret Encryption if a new, more secure encryption algorithm is defined in a newer version of Telegram or the 2FA password is updated.

Passport Secret Encryption

First of all, server-side passport parameters are fetched, schema:

account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password;

securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;


---functions---

account.getPassword#548a30f5 = account.Password;

When Telegram Passport is first used, the client generates a passport_secret (a 32-byte number with the modulo 255 sum of bytes equal to 239), using a part of server-generated random secure_random from account.password as an additional source of entropy for OpenSSL (when re-encrypting the passport_secret with a more secure algorithm or after a 2FA password change, the previous passport_secret is used, instead). Then passport_secret is then encrypted using the user's password and hashed using the schema and parameters specified in the new_algo field of account.password.

The server should always return a securePasswordKdfAlgoPBKDF2HMACSHA512iter100000 constructor in the new_algo field. If securePasswordKdfAlgoUnknown is returned, the remotely stored secret is encrypted using a new algorithm, not supported by the current client: the user should update their app.

The other constructors may be used only when decrypting old passport parameters generated by a legacy client; in this case, the passport secret should be re-encrypted and updated using new_algo.

Subsequently, the client receives the encrypted passport_secret from the server and decrypts it after the user enters their password ».

In case the password is changed or a more secure algorithm is introduced in an update of the API, the client re-encrypts the passport_secret using the new password. If the password is disabled, all Telegram Passport data is lost.

Passport Secret Decryption

Schema:

securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;

secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;

account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;

---functions---

account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings;

The client requests the user's 2FA password and generates the SRP paramaters to be passed to account.getPasswordSettings.

If the password is correct, an account.passwordSettings constructor with secureSecretSettings is returned.

encrypted_passport_secret, passport_secret_fingerprint parameters are extracted from the secureSecretSettings constructor:

encrypted_passport_secret = secureSecretSettings.secure_secret
passport_secret_fingerprint = secureSecretSettings.secure_id

The combined passport_secret_salt is extracted from the SecurePasswordKdfAlgo.

passport_secret_salt = SecurePasswordKdfAlgo.salt

Similar to passport secret encryption, the following process is used to decrypt and verify the encrypted_passport_secret:

  • The user's 2FA plaintext password is hashed using the specified algorithm.

  • The secret_key and iv parameters are extracted from the generated password_hash

    secret_key = slice( password_hash, 0, 32 )
    iv = slice( password_hash, 32, 16 )
  • The encrypted_passport_secret is decrypted using AES256-CBC with the key secret_key and iv:

    passport_secret = AES256_CBC_DEC(encrypted_passport_secret, secret_key, iv)
  • The passport_secret is verified by generating and checking the fingerprint:

    my_passport_secret_fingerprint = long( slice( SHA256( passport_secret ), 0, 8 ) )

    The client must verify that passport_secret_fingerprint is indeed equal to my_passport_secret_fingerprint.

The passport_secret can now be used to decrypt encrypted passport data stored on telegram servers:

Data and File Encryption

Encryption

To encrypt Telegram Passport data, the client generates a data_secret (a 32-byte number with the modulo 255 sum of bytes equal to 239). The data is encrypted according to the following scheme:

Packing

SecureData
secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
  • data is an encrypted and padded (see Encryption) JSON-serialized object of one of the following types: PersonalDetails, IdDocumentData, ResidentialAddress, depending on the chosen type. Data must be in JSON format and not TL, as it has to be passed directly to the service using E2E encryption, without the bot API middleman to convert TL objects.
  • data_hash is the data_hash
  • secret is the encrypted_data_secret

Data is an encrypted and padded JSON-serialized object of one of the specified JSON types, depending on the chosen type.

Chosen type JSON object
secureValueTypePersonalDetails PersonalDetails
secureValueTypePassport IdDocumentData
secureValueTypeDriverLicense IdDocumentData
secureValueTypeIdentityCard IdDocumentData
secureValueTypeInternalPassport IdDocumentData
secureValueTypeAddress ResidentialAddress
InputSecureFile
inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;

---functions---

upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;

Files (JPG format, max. 10 MB) are encrypted and padded (see Encryption), and then uploaded chunk by chunk as described in files », except that instead of generating an inputFile, an inputSecureFile should be generated, instead.

  • As for secret chat files, the md5_checksum is to be set to the MD5 hash of the encrypted file, for a server-side integrity check.
  • The file_hash field should be set to the data_hash of the data.
  • The secret field is the encrypted_data_secret.
SecurePlainData
securePlainPhone#7d6099dd phone:string = SecurePlainData;
securePlainEmail#21ec5a5f email:string = SecurePlainData;

emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose;

---functions---

account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode;
account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified;

auth.resendCode#cae47523 flags:# phone_number:string phone_code_hash:string reason:flags.0?string = auth.SentCode;
auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool;

The email/phone is passed in plaintext using the respective SecurePlainData constructor. To verify a phone number or email and use it in Telegram Passport, use the appropriate methods:

The flow is similar to the one used for logging in:

For more info, see the authorization docs.

When to use each constructor.

inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;

secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
secureValueTypePassport#3dac6a00 = SecureValueType;
secureValueTypeDriverLicense#6e425c4 = SecureValueType;
secureValueTypeIdentityCard#a0d0744b = SecureValueType;
secureValueTypeInternalPassport#99a48f23 = SecureValueType;
secureValueTypeAddress#cbe31e26 = SecureValueType;
secureValueTypeUtilityBill#fc36954e = SecureValueType;
secureValueTypeBankStatement#89137c0d = SecureValueType;
secureValueTypeRentalAgreement#8b883488 = SecureValueType;
secureValueTypePassportRegistration#99e3806a = SecureValueType;
secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType;

securePlainPhone#7d6099dd phone:string = SecurePlainData;
securePlainEmail#21ec5a5f email:string = SecurePlainData;

secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;

inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;

The schema for the inputSecureValue constructor defines the constructor to use for each field.

Name Type Description
type SecureValueType Secure passport value type
data flags.0?SecureData Encrypted Telegram Passport element data
front_side flags.1?InputSecureFile Encrypted passport file with the front side of the document
reverse_side flags.2?InputSecureFile Encrypted passport file with the reverse side of the document
selfie flags.3?InputSecureFile Encrypted passport file with a selfie of the user holding the document
translation flags.6?Vector<InputSecureFile> Array of encrypted passport files with translated versions of the provided documents
files flags.4?Vector<InputSecureFile> Array of encrypted passport files with photos the of the documents
plain_data flags.5?SecurePlainData Plaintext verified passport data

Here's a list of possible SecureValueTypes, and the parameters that can be set/requested when using each type.

Type Allowed fields
secureValueTypeEmail plain_data
secureValueTypePhone plain_data
secureValueTypePersonalDetails data
secureValueTypePassport data, front_side, selfie, translation
secureValueTypeDriverLicense data, front_side, reverse_side, selfie, translation
secureValueTypeIdentityCard data, front_side, reverse_side, selfie, translation
secureValueTypeInternalPassport data, front_side, selfie, translation
secureValueTypeAddress data
secureValueTypeUtilityBill files, translation
secureValueTypeBankStatement files, translation
secureValueTypeRentalAgreement files, translation
secureValueTypePassportRegistration files, translation
secureValueTypeTemporaryRegistration files, translation

Fetching and deleting stored passport data

---functions---

account.getAllSecureValues#b288bc7d = Vector<SecureValue>;
account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;

The above methods can be used to fetch or remove encrypted Telegram Passport files stored in the Telegram Cloud by document type.

Passport Credentials

When a service requests data, it passes a nonce to the client. The nonce is a cryptographically secure unique identifier which allows the service to identify a request when receiving data as well as confirm the integrity of the data. The Telegram server doesn't have access to this nonce.

Once the user authorizes the Telegram Passport data transfer, the client forms the credentials (Credentials JSON object). Credentials contain the data_hash and data_secret from each element of Telegram Passport to which the user has allowed access. In addition to this, the credentials will always contain the nonce that the client received from the service at the initiation of the request.

Credentials are then passed to the service through the Bot API in encrypted form. To encrypt the credentials, the client generates a credentials_secret (a 32-byte number with the modulo 255 sum of bytes equal to 239). Then the credentials are encrypted according to the following scheme:

  • Credentials are padded to a length which is divisible by 16 bytes. To achieve this, 32 to 255 bytes are added at the beginning, where the first byte always holds the number of added bytes and the rest are random.

  • A hash of the padded credentials credentials_hash is calculated:

    credentials_hash = SHA256( credentials )
  • The encryption key credentials_key is calculated:

    credentials_secret_hash = SHA512( credentials_secret + credentials_hash )
    credentials_key = slice( credentials_secret_hash, 0, 32 )
    iv = slice( credentials_secret_hash, 32, 16 )
  • Credentials are encrypted using AES256-CBC with the key credentials_key and iv.

    encrypted_credentials = AES256-CBC-ENC(credentials, credentials_key, iv)
  • credentials_secret is encrypted with the public RSA-key of the service with OPENSSL_PKCS1_OAEP_PADDING.

    encrypted_credentials_secret = RSA-ENC(credentials_secret, key, OPENSSL_PKCS1_OAEP_PADDING)
  • The encrypted credentials are passed to the service via the MTProto API together with the encrypred credentials_secret and credentials_hash. Along with the credentials, the service receives from the Telegram Cloud the data it requested in encrypted form. See Submitting the Passport Form and PassportData:

    secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
  • data is the encrypted_credentials

  • hash is the credentials_hash

  • secret is the encrypted_credentials_secret

Then the service decrypts the data as described here.