Подпись URL-схемы ключом доступа

Чтобы пользователи вашего приложения или сайта не сталкивались с ограничениями при запуске мобильного приложения Яндекс Карты:

  1. Получите ключ-доступа. Для этого заполните форму.

  2. Используйте URL следующего формата:

    yandexmaps://maps.yandex.ru/?{параметры}&client=<идентификатор клиента>&signature=<подпись>
    
    client
    Идентификатор клиента, который вы получаете вместе с ключом. Даже если у вас несколько приложений, потребуется только один идентификатор.
    signature
    Подпись — это строка, которую нужно сформировать из исходного URL с помощью ключа доступа.

Как сформировать URL с подписью

  1. Составьте URL, соответствующий задаче, которую должно выполнить приложение. Следующий URL позволяет показать точку на карте:

    yandexmaps://maps.yandex.ru?ll=55.75,37.64&z=14
    
  2. Добавьте к URL параметр client, в качестве значения укажите идентификатор:

    yandexmaps://maps.yandex.ru?ll=55.75,37.64&z=14&client=007
    
  3. Вычислите хэш-сумму от URL, полученного на предыдущем шаге, с помощью хеш-функции SHA-256. Затем зашифруйте полученную хэш-сумму с помощью ключа доступа.

    Ключ доступа — это RSA-ключ, который вы получаете после регистрации. Физически это запись в текстовом файле. Называться файл может, например, так: key.pem.

    Ниже приведен пример шифрования с помощью утилиты OpenSSL.

    Bash

    $ echo -n 'yandexmaps://maps.yandex.ru?ll=55.75,37.64&z=14&client=007' |
    openssl dgst -sha256 -sign key.pem |
    base64 |
    tr -d '\n' |
    python -c "import urllib, sys; print urllib.quote(sys.stdin.read(), safe='')"
    
  4. Сформируйте US-ASCII строку подписи.

    Чтобы передать двоичные данные в URL, их нужно перекодировать в набор символов US-ASCII. Поэтому сначала из двоичных данных сформируйте строку ASCII символов, используя base64-представление. Затем перекодируйте полученную строку в US-ASCII, используя механизм кодирования URL.

    Python

    from Crypto.PublicKey import RSA
    from Crypto.Hash import SHA256
    from Crypto.Signature import PKCS1_v1_5
    from base64 import b64encode
    from urllib import quote
    
    print "sign"
    with open("key.pem") as f:
    private_key = RSA.importKey(f.read())
    
    h = SHA256.new(src_data)
    print "hash value"
    print h.hexdigest()
    
    signer = PKCS1_v1_5.new(private_key)
    signature = quote(b64encode(signer.sign(h)), safe='')
    
  5. Используйте строку, полученную на предыдущем шаге, в качестве значения параметра signature. Пример URL с подписью:

    yandexmaps://maps.yandex.ru?ll=55.75,37.64&z=14&client=007&signature=JYEYuBc7154%2Be%2BHHW8RKG0O2dVx%2B%2B...
    

Как конвертировать RSA-ключ

Мы генерируем RSA-ключи в формате PEM (стандарт PKCS1). В мобильных операционных системах используются и другие форматы. Чтобы обеспечить совместимость, выполните конвертацию.

Примеры конвертации с помощью утилиты OpenSSL приведены ниже.

Конвертация текстового PEM-файла в бинарный DER. В результате получим ключ в формате DER (стандарт PKCS1):

$ openssl rsa -in key.pem -out key.der -outform DER

Конвертация PEM-ключа PKCS1 в ключ PKCS8:

$ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in key-pkcs1.pem -out key-pkcs8.pem

Конвертация PEM-ключа PKCS1 в ключ в формате DER (стандарт PKCS8):

$ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in key-pkcs1.pem -out key-pkcs8.pem

$ openssl pkcs8 -topk8 -inform PEM -outform DER -in key-pkcs8.pem -out key.der -nocrypt

Примеры кода нативных приложений

import android.content.Intent;
import android.net.Uri;
import android.util.Base64;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;


public class YaMapsStarter {

    private final String PRIVATE_KEY;

    // Формирует подпись с помощью ключа.
    public String sha256rsa(String key, String data) throws SecurityException {
        String trimmedKey = key.replaceAll("-----\\w+ PRIVATE KEY-----", "")
                                .replaceAll("\\s", "");

        try {
            byte[]         result    = Base64.decode(trimmedKey, Base64.DEFAULT);
            KeyFactory     factory   = KeyFactory.getInstance("RSA");
            EncodedKeySpec keySpec   = new PKCS8EncodedKeySpec(result);
            Signature      signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(factory.generatePrivate(keySpec));
            signature.update(data.getBytes());

            byte[] encrypted = signature.sign();
            return Base64.encodeToString(encrypted, Base64.NO_WRAP);
        } catch (Exception e) {
            throw new SecurityException("Error calculating cipher data. SIC!");
        }
    }

    // Формирует URI с подписью и запускает Яндекс Карты.
    public void openMap() {
        Uri uri = Uri.parse("yandexmaps://maps.yandex.ru").buildUpon()
            .appendQueryParameter("ll", 55.75,37.64)
            .appendQueryParameter("z", "14")
            .appendQueryParameter("client", "007").build();

        uri = uri.buildUpon()
           .appendQueryParameter("signature", sha256rsa(PRIVATE_KEY, uri.toString()))
           .build();

        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        intent.setPackage("ru.yandex.yandexmaps");
        startActivity(intent);
    }
}