Подпись ключом доступа

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

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

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

    yandexnavi://<путь>?<параметры>&client=<идентификатор клиента>&signature=<подпись>
    

    client

    Идентификатор клиента, который вы получаете вместе с ключом. Даже если у вас несколько приложений, потребуется только один идентификатор.

    signature

    Подпись — это строка, которую нужно сформировать из исходного URL с помощью ключа доступа.

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

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

    yandexnavi://show_point_on_map?lat=55.75&lon=37.64&zoom=14
    
  2. Добавьте к URL параметр client, в качестве значения укажите идентификатор:

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

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

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

    Bash

    $ echo -n 'yandexnavi://show_point_on_map?lat=55.75&lon=37.64&zoom=14&client=007'
    |
        openssl dgst -sha256 -sign key.pem |
        bаse64 |
        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 с подписью:

    yandexnavi://show_point_on_map?lat=55.75&lon=37.64&zoom=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 YaNaviStarter {

    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 buildRoute() {     
        Uri uri = Uri.parse("yandexnavi://build_route_on_map").buildUpon()
            .appendQueryParameter("lat_to", "55.680559")
            .appendQueryParameter("lon_to", "37.549246")
            .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.yandexnavi");
        startActivity(intent);
    }
}
// Зашифровывает SHA256 с помощью DER-ключа.
func signString(string: String, key: SecKey) -> String {
  let messageData = string.data(using:String.Encoding.utf8)!
  var hash = Data(count: Int(CC_SHA256_DIGEST_LENGTH))

   _ = hash.withUnsafeMutableBytes {digestBytes in
    messageData.withUnsafeBytes {messageBytes in
      CC_SHA256(messageBytes, CC_LONG(messageData.count), digestBytes)
    }
  }

  let signature = SecKeyCreateSignature(key,
    SecKeyAlgorithm.rsaSignatureDigestPKCS1v15SHA256,
    hash as CFData,
    nil) as Data?

  return (signature?.base64EncodedString())!
}
    
// Преобразует PEM-ключ в DER-ключ.
func loadCert() -> SecKey {
  let certificateData = NSData(
    contentsOf:Bundle.main.url(forResource: "private_key", withExtension: "der")!
  )

  let options: [String: Any] =
    [kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
    kSecAttrKeySizeInBits as String: 512]

  let key = SecKeyCreateWithData(certificateData!,
    options as CFDictionary,
    nil)

  return key!
}
    
// Формирует URL с подписью и запускает Яндекс Навигатор.
func buildRoute() {
    var compoments = URLComponents(string: "yandexnavi://build_route_on_map")!
    compoments.queryItems = [
        URLQueryItem(name: "lat_to", value: "55.74"),
        URLQueryItem(name: "lon_to", value: "37.64"),
        URLQueryItem(name: "client", value: "007")
    ]

    let signature = signString(string: compoments.string!, key: loadCert())
    let final = compoments.string!.appending(
        "&signature=" + signature.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
    )

    UIApplication.shared.open(URL(string: final)!)
}