Статьи

Создание цифровых подписей с помощью Swift

Основное назначение цифровой подписи — проверка целостности некоторой информации. Для простого примера, скажем, у вас есть файл, который был передан по сети, и вы хотите проверить, что весь файл был передан правильно. В этом случае вы бы использовали контрольную сумму.

«Контрольная сумма — это небольшая величина, полученная из блока цифровых данных с целью обнаружения ошибок, которые могли быть внесены при ее передаче или хранении» — Википедия

Как мы можем получить эту контрольную сумму? Лучший вариант — использовать хеш. Хеш-функция будет принимать переменное количество данных и выводить сигнатуру фиксированной длины. Например, мы могли бы опубликовать файл вместе с его хешем онлайн. Когда кто-то загружает файл, он может запустить ту же хеш-функцию для своей версии файла и сравнить результат. Если хеши совпадают, то скопированный или загруженный файл совпадает с оригиналом.

Хеш также является односторонней функцией. Учитывая полученный результат, в вычислительном отношении не существует способа реверсировать этот хеш, чтобы выявить исходный ввод. SHA, Secure Hash Algorithm, является хорошо известным стандартом, который относится к группе хеш-функций, которые имеют это свойство и некоторые другие, что делает их полезными для цифровых подписей.

SHA прошел много итераций с момента его первой публикации. Известно, что первая и вторая итерации, SHA-0 и SHA-1, имеют серьезные недостатки . Они больше не одобрены для реализации безопасности: их обычно не следует использовать для приложений, полагающихся на безопасность. Однако семейство SHA-2 включает в себя версии, называемые SHA-256 и SHA-512, и они считаются безопасными. «256» и «512» просто относятся к результирующему количеству произведенных битов. Для этого урока мы будем использовать SHA-512.

Примечание. Другим старым популярным алгоритмом хеширования был MD5. Также было обнаружено, что есть существенные недостатки.

Использование SHA отлично подходит для проверки того, были ли данные случайно повреждены, но это не мешает злоумышленнику вмешиваться в эти данные. Учитывая, что выходные данные хеша имеют фиксированный размер, все, что нужно злоумышленнику, это выяснить, какой алгоритм использовался с учетом размера выходных данных, изменить данные и заново вычислить хэш. Что нам нужно, так это некоторая секретная информация, добавляемая в микс при хешировании данных, чтобы злоумышленник не мог пересчитать хеш без знания секрета. Это называется кодом аутентификации хэш-сообщения (HMAC).

HMAC может подтвердить подлинность фрагмента информации или сообщения, чтобы убедиться, что оно отправлено правильным отправителем и что информация не была изменена. Обычный сценарий — это когда вы общаетесь с сервером с внутренним API для вашего приложения. Может быть важно пройти проверку подлинности, чтобы гарантировать, что только ваше приложение может общаться с API. API будет иметь контроль доступа к конкретному ресурсу, такому как конечная точка / register_user . Клиент должен будет подписать свой запрос к конечной точке / register_user , чтобы успешно использовать его.

При подписании запроса обычно выбирают отдельные части запроса, такие как параметры POST и URL, и объединяют их в строку. Взятие согласованных элементов и приведение их в определенный порядок называется канонизацией . В HMAC объединенная строка хешируется вместе с секретным ключом для создания подписи . Вместо того, чтобы называть это хешем, мы используем термин подпись так же, как подпись человека в реальной жизни используется для проверки личности или целостности. Подпись добавляется обратно в запрос клиента как заголовок запроса (обычно также называемый «Подпись»). Подпись иногда называется дайджестом сообщения, но эти два термина могут использоваться взаимозаменяемо.

На стороне API сервер повторяет процесс соединения строк и создания подписи. Если подписи совпадают, это доказывает, что приложение должно обладать секретом. Это подтверждает личность приложения. Поскольку конкретные параметры запроса также были частью подписываемой строки, это также гарантирует целостность запроса. Он не позволяет атакующему, например, выполнить атаку «человек посередине» и изменить параметры запроса по своему вкусу.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class func hmacExample()
{
    //Some URL example…
    let urlString = «https://example.com»
    let postString = «id=123»
    let url = URL.init(string: urlString)
    var request = URLRequest(url: url!)
    request.httpMethod = «POST»
    request.httpBody = postString.data(using: .utf8)
    let session = URLSession.shared
 
    //Create a signature
    let stringToSign = request.httpMethod!
    print(«The string to sign is : «, stringToSign)
    if let dataToSign = stringToSign.data(using: .utf8)
    {
        let signingSecret = «4kDfjgQhcw4dG6J80QnvRFbtuJfkgitH6phkLN90»
        if let signingSecretData = signingSecret.data(using: .utf8)
        {
            let digestLength = Int(CC_SHA512_DIGEST_LENGTH)
            let digestBytes = UnsafeMutablePointer<UInt8>.allocate(capacity:digestLength)
             
            CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA512), [UInt8](signingSecretData), signingSecretData.count, [UInt8](dataToSign), dataToSign.count, digestBytes)
             
            //base64 output
            let hmacData = Data(bytes: digestBytes, count: digestLength)
            let signature = hmacData.base64EncodedString()
            print(«The HMAC signature in base64 is » + signature)
             
            //or HEX output
            let hexString = NSMutableString()
            for i in 0..<digestLength
            {
                hexString.appendFormat(«%02x», digestBytes[i])
            }
            print(«The HMAC signature in HEX is», hexString)
             
            //Set the Signature header
            request.setValue(signature, forHTTPHeaderField: «Signature»)
        }
    }
     
    //Start your request…
    //let task = session.dataTask(with: request, completionHandler: { (data, response, error) in
        // …
    //})
    //task.resume()
     
}

В этом коде функция CCHmac принимает параметр для типа используемой хэш-функции, а также две байтовые строки и их длины — сообщение и секретный ключ. Для обеспечения максимальной безопасности используйте как минимум 256-битный (32 байтный) ключ, сгенерированный из криптографически безопасного генератора случайных чисел . Чтобы убедиться, что на другой стороне все работает правильно, запустите пример, а затем введите секретный ключ и сообщение на этом удаленном сервере и убедитесь, что выходные данные совпадают.

Вы также можете добавить заголовок временной метки к запросу и строку подписи, чтобы сделать запрос более уникальным. Это может помочь API отсеять атаки воспроизведения. Например, API может отбросить запрос, если отметка времени устарела на 10 минут.

Хотя полезно придерживаться безопасных версий SHA, оказывается, что многие из уязвимостей небезопасных версий SHA не относятся к HMAC. По этой причине вы можете увидеть использование SHA1 в рабочем коде. Однако, с точки зрения связей с общественностью, это может выглядеть плохо, если вам придется объяснять, почему, криптографически говоря, в этом контексте можно использовать SHA1. Многие из недостатков SHA1 связаны с так называемыми атаками столкновений . Аудиторы кода или исследователи безопасности могут ожидать, что ваш код будет защищен от столкновений, независимо от контекста. Кроме того, если вы пишете модульный код, в котором вы можете поменять функцию подписи на другую в будущем, вы можете забыть обновить небезопасные хеш-функции. Поэтому мы все равно будем придерживаться SHA-512 в качестве нашего алгоритма выбора.

Процессор HMAC работает быстро, но одним недостатком является проблема обмена ключами. Как мы можем сообщить друг другу, что такое секретный ключ, без его перехвата? Например, возможно, вашему API потребуется динамически добавлять или удалять несколько приложений или платформ из белого списка. В этом случае приложения должны будут зарегистрироваться, а секрет должен быть передан приложению после успешной регистрации. Вы можете отправить ключ по HTTPS и использовать закрепление SSL , но даже в этом случае всегда есть опасение, что ключ каким-то образом будет украден во время обмена. Решение проблемы обмена ключами состоит в том, чтобы сгенерировать ключ, который вообще не должен покидать устройство. Это может быть достигнуто с помощью криптографии с открытым ключом, и очень популярным и принятым стандартом является RSA.

RSA расшифровывается как Rivest-Shamir-Adleman (авторы криптосистемы). Это включает в себя использование трудности сложения фактора произведения двух очень больших простых чисел. RSA может использоваться для шифрования или аутентификации, хотя для этого примера мы собираемся использовать его только для аутентификации. RSA генерирует два ключа, открытый и закрытый, который мы можем выполнить с SecKeyGeneratePair функции SecKeyGeneratePair . При использовании для аутентификации закрытый ключ используется для создания подписи, а открытый ключ проверяет подпись. Учитывая открытый ключ, в вычислительном отношении невозможно получить закрытый ключ.

Следующий пример демонстрирует то, что Apple и все популярные компании игровых консолей используют при распространении своего программного обеспечения. Допустим, ваша компания периодически создает и доставляет файл, который пользователи будут перетаскивать в раздел обмена файлами вашего приложения в iTunes . Вы хотите убедиться, что файлы, которые вы отправляете, никогда не изменяются перед анализом в приложении. Ваша компания будет хранить и защищать закрытый ключ, который она использует для подписи файлов. В комплекте приложения находится копия открытого ключа, используемого для проверки файла. Учитывая, что закрытый ключ никогда не передается и не включается в приложение, злоумышленник не может подписать свои версии файлов (кроме взлома компании и кражи закрытого ключа).

Мы будем использовать SecKeyRawSign чтобы подписать файл. Было бы медленно подписывать все содержимое файла с использованием RSA, поэтому вместо этого подписывается хеш файла. Кроме того, данные, передаваемые в RSA, также должны быть хешированы перед подписанием из-за некоторых недостатков безопасности .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@available(iOS 10.0, *)
class FileSigner
{
    private var publicKey : SecKey?
    private var privateKey : SecKey?
     
    func generateKeys() -> String?
    {
        var publicKeyString : String?
        //generate a new keypair
        let parameters : [String : AnyObject] =
        [
            kSecAttrKeyType as String : kSecAttrKeyTypeRSA,
            kSecAttrKeySizeInBits as String : 4096 as AnyObject,
        ]
        let status = SecKeyGeneratePair(parameters as CFDictionary, &publicKey, &privateKey)
         
        //—Save your key here— //
         
        //Convert the SecKey object into a representation that we can send over the network
        if status == noErr && publicKey != nil
        {
            if let cfData = SecKeyCopyExternalRepresentation(publicKey!, nil)
            {
                let data = cfData as Data
                publicKeyString = data.base64EncodedString()
            }
        }
         
        return publicKeyString
    }
     
    func signFile(_ path : String) -> String?
    {
        var signature : String?
         
        if let fileData = FileManager.default.contents(atPath: path)
        {
            if (privateKey != nil)
            {
                //hash the message first
                let digestLength = Int(CC_SHA512_DIGEST_LENGTH)
                let hashBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: digestLength)
                CC_SHA512([UInt8](fileData), CC_LONG(fileData.count), hashBytes)
                 
                //sign
                let blockSize = SecKeyGetBlockSize(privateKey!) //in the case of RSA, modulus is the same as the block size
                var signatureBytes = [UInt8](repeating:0, count:blockSize)
                var signatureDataLength = blockSize
                let status = SecKeyRawSign(privateKey!, .PKCS1SHA512, hashBytes, digestLength, &signatureBytes, &signatureDataLength)
                if status == noErr
                {
                    let data = Data(bytes: signatureBytes, count: signatureDataLength)
                    signature = data.base64EncodedString()
                }
            }
        }
        return signature
    }
}

В этом коде мы использовали функцию CC_SHA512 чтобы снова указать SHA-512. (В отличие от HMAC RSA становится небезопасным, если базовая хеш-функция небезопасна.) Мы также используем 4096 в качестве размера ключа, который kSecAttrKeySizeInBits параметром kSecAttrKeySizeInBits . 2048 минимальный рекомендуемый размер . Это сделано для того, чтобы мощная сеть компьютерных систем не взломала ключ RSA (под взломом я имею в виду факторизацию ключа RSA — также известную как факторизация открытого модуля ). По оценкам группы RSA, 2048-битные ключи могут стать взломанными за некоторое время до 2030 года. Если вы хотите, чтобы ваши данные были безопасны после этого времени, то лучше выбрать больший размер ключа, например 4096.

Сгенерированные ключи имеют форму объектов SecKey . Проблема реализации Apple SecKey заключается в том, что она не включает в себя всю необходимую информацию, составляющую открытый ключ, поэтому она не является действительным сертификатом X.509 в кодировке DER. Добавление недостающей информации обратно в формат для приложения iOS или OS X, даже серверных платформ, таких как PHP, требует некоторой работы и включает работу в формате, известном как ASN.1 . К счастью, это было исправлено в iOS 10 с новыми функциями SecKey для генерации, экспорта и импорта ключей.

Приведенный ниже код показывает другую сторону связи — класс, который принимает открытый ключ через SecKeyCreateWithData для проверки файлов с SecKeyRawVerify функции SecKeyRawVerify .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@available(iOS 10.0, *)
class FileVerifier
{
    private var publicKey : SecKey?
     
    func addPublicKey(_ keyString : String) -> Bool
    {
        var success = false
        if let keyData = Data.init(base64Encoded: keyString)
        {
            let parameters : [String : AnyObject] =
            [
                kSecAttrKeyType as String : kSecAttrKeyTypeRSA,
                kSecAttrKeyClass as String : kSecAttrKeyClassPublic,
                kSecAttrKeySizeInBits as String : 4096 as AnyObject,
                kSecReturnPersistentRef as String : true as AnyObject
            ]
            publicKey = SecKeyCreateWithData(keyData as CFData, parameters as CFDictionary, nil)
                 
            if (publicKey != nil)
            {
                success = true
            }
        }
        return success
    }
     
    func verifyFile(_ path : String, withSignature signature : String) -> Bool
    {
        var success = false
        if (publicKey != nil)
        {
            if let fileData = FileManager.default.contents(atPath: path)
            {
                if let signatureData = Data.init(base64Encoded: signature)
                {
                    //hash the message first
                    let digestLength = Int(CC_SHA512_DIGEST_LENGTH)
                    let hashBytes = UnsafeMutablePointer<UInt8>.allocate(capacity:digestLength)
                    CC_SHA512([UInt8](fileData), CC_LONG(fileData.count), hashBytes)
                     
                    //verify
                    let status = signatureData.withUnsafeBytes {signatureBytes in
                        return SecKeyRawVerify(publicKey!, .PKCS1SHA512, hashBytes, digestLength, signatureBytes, signatureData.count)
                    }
                    if status == noErr
                    {
                        success = true
                    }
                    else
                    {
                        print(«Signature verify error») //-9809 : errSSLCrypto, etc
                    }
                }
            }
        }
        return success
    }
}

Вы можете попробовать это и убедиться, что он работает, используя простой тест, подобный следующему:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if #available(iOS 10.0, *)
{
    guard let plistPath = Bundle.main.path(forResource: «Info», ofType: «plist») else { print(«Couldn’t get plist»);
     
    let fileSigner = FileSigner()
     
    DispatchQueue.global(qos: .userInitiated).async // RSA key gen can be long running work
    {
        guard let publicKeyString = fileSigner.generateKeys() else { print(«Key generation error»);
         
        // Back to the main thread
        DispatchQueue.main.async
        {
            guard let signature = fileSigner.signFile(plistPath) else { print(«No signature»);
             
            //Save the signature on the other end
            let fileVerifier = FileVerifier()
            guard fileVerifier.addPublicKey(publicKeyString) else { print(«Key was not added»);
             
            let success = fileVerifier.verifyFile(plistPath, withSignature: signature)
            if success
            {
                print(«Signatures match!»)
            }
            else
            {
                print(«Signatures do not match.»)
            }
        }
    }
}

У RSA есть один недостаток — генерация ключей идет медленно! Время генерации ключей зависит от размера ключа. На более новых устройствах 4096-битный ключ занимает всего несколько секунд, но если вы запустите этот код на iPod Touch 4-го поколения, это может занять около минуты. Это хорошо, если вы просто генерируете ключи несколько раз на компьютере, но что происходит, когда нам нужно часто генерировать ключи на мобильном устройстве? Мы не можем просто уменьшить размер ключа, потому что это снижает безопасность.

Так в чем же решение? Итак, криптография на основе эллиптических кривых (ECC) является перспективным подходом — новым набором алгоритмов, основанных на эллиптических кривых над конечными полями. Ключи ECC намного меньше по размеру и генерируются быстрее, чем ключи RSA. Ключ длиной всего 256 бит обеспечивает очень высокий уровень безопасности! Чтобы воспользоваться ECC, нам не нужно много менять код. Мы можем подписать наши данные с помощью той же функции SecKeyRawSign а затем настроить параметры для использования алгоритма цифровой подписи эллиптической кривой (ECDSA).

Совет: Для получения дополнительной информации о реализации RSA вы можете обратиться к вспомогательной библиотеке SwiftyRSA , которая ориентирована как на шифрование, так и на подписывание сообщений.

Представьте себе следующий сценарий: приложение чата позволяет пользователям отправлять друг другу личные сообщения, но вы хотите убедиться, что злоумышленник не изменил сообщение на пути к другому пользователю. Посмотрим, как вы могли бы обезопасить их общение с помощью криптографии.

Во-первых, каждый пользователь генерирует пару ключей открытого и закрытого ключей на своем мобильном устройстве. Их закрытые ключи хранятся в памяти и никогда не покидают устройство, в то время как открытые ключи передаются друг другу. Как и прежде, закрытый ключ используется для подписи отправляемых данных, а открытый ключ — для проверки. Если злоумышленник получит открытый ключ во время передачи, все, что можно сделать, это проверить целостность исходного сообщения от отправителя. Злоумышленник не может изменить сообщение, поскольку у него нет закрытого ключа, необходимого для восстановления подписи.

Есть еще один опыт использования ECDSA на iOS. Мы можем использовать тот факт, что в настоящее время ключи эллиптической кривой являются единственными, которые могут храниться в безопасном анклаве устройства. Все остальные ключи хранятся в цепочке для ключей, которая шифрует свои элементы в области хранения устройства по умолчанию. На устройствах, имеющих такое устройство, защищенный анклав располагается отдельно от процессора, а хранилище ключей реализуется аппаратно без прямого доступа к программному обеспечению. Безопасный анклав может хранить закрытый ключ и работать с ним для получения вывода, который отправляется в ваше приложение, даже не подвергая действительный закрытый ключ, загружая его в память!

Я добавлю поддержку для создания закрытого ключа ECDSA в безопасном анклаве, добавив параметр kSecAttrTokenIDSecureEnclave для параметра kSecAttrTokenID . Мы можем начать этот пример с объекта User который будет генерировать пару ключей при инициализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@available(iOS 9.0, *)
class User
{
    public var publicKey : SecKey?
    private var privateKey : SecKey?
    private var recipient : User?
     
    init(withUserID id : String)
    {
        //if let access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, [.privateKeyUsage /*, .userPresence] authentication UI to get the private key */], nil) //Force store only if passcode or Touch ID set up…
        if let access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, [.privateKeyUsage], nil) //Keep private key on device
        {
            let privateTagString = «com.example.privateKey.»
            let privateTag = privateTagString.data(using: .utf8)!
            let privateKeyParameters : [String : AnyObject] = [kSecAttrIsPermanent as String : true as AnyObject,
                                                               kSecAttrAccessControl as String : access as AnyObject,
                                                               kSecAttrApplicationTag as String : privateTag as AnyObject,
                ]
         
            let publicTagString = «com.example.publicKey.»
            let publicTag = publicTagString.data(using: .utf8)!
            let publicKeyParameters : [String : AnyObject] = [kSecAttrIsPermanent as String : false as AnyObject,
                                                              kSecAttrApplicationTag as String : publicTag as AnyObject,
                ]
             
            let keyPairParameters : [String : AnyObject] = [kSecAttrKeySizeInBits as String : 256 as AnyObject,
                                                            kSecAttrKeyType as String : kSecAttrKeyTypeEC,
                                                            kSecPrivateKeyAttrs as String : privateKeyParameters as AnyObject,
                                                            kSecAttrTokenID as String : kSecAttrTokenIDSecureEnclave as AnyObject, //Store in Secure Enclave
                                                            kSecPublicKeyAttrs as String : publicKeyParameters as AnyObject]
             
            let status = SecKeyGeneratePair(keyPairParameters as CFDictionary, &publicKey, &privateKey)
            if status != noErr
            {
                print(«Key generation error»)
            }
        }
    }
     
    //…

Далее мы создадим несколько вспомогательных и примерных функций. Например, класс позволит пользователю начать разговор и отправить сообщение. Конечно, в вашем приложении вы должны настроить это, чтобы включить ваши конкретные настройки сети.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//…
    
   private func sha512Digest(forData data : Data) -> Data
   {
       let len = Int(CC_SHA512_DIGEST_LENGTH)
       let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
       CC_SHA512((data as NSData).bytes, CC_LONG(data.count), digest)
       return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len) as Data
   }
    
   public func initiateConversation(withUser user : User) -> Bool
   {
       var success = false
       if publicKey != nil
       {
           user.receiveInitialization(self)
           recipient = user
           success = true
       }
       return success
   }
    
   public func receiveInitialization(_ user : User)
   {
        recipient = user
   }
    
   public func sendMessage(_ message : String)
   {
       if let data = message.data(using: .utf8)
       {
           let signature = self.signData(plainText: data)
           if signature != nil
           {
               self.recipient?.receiveMessage(message, withSignature: signature!)
           }
       }
   }
    
   public func receiveMessage(_ message : String, withSignature signature : Data)
   {
       let signatureMatch = verifySignature(plainText: message.data(using: .utf8)!, signature: signature)
       if signatureMatch
       {
           print(«Received message. Signature verified. Message is : «, message)
       }
       else
       {
           print(«Received message. Signature error.»)
       }
   }
    
   //…

Далее мы сделаем фактическую подпись и проверку. ECDSA, в отличие от RSA, не требует хеширования до подписания. Однако, если вы хотите иметь функцию, в которой алгоритм может быть легко заменен без внесения многих изменений, то вполне нормально продолжать хеширование данных перед подписанием.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//…
     
    func signData(plainText: Data) -> Data?
    {
        guard privateKey != nil else
        {
            print(«Private key unavailable»)
            return nil
        }
         
        let digestToSign = self.sha512Digest(forData: plainText)
        let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 512) //512 — overhead
        var signatureLength = 512
         
        let status = SecKeyRawSign(privateKey!, .PKCS1SHA512, [UInt8](digestToSign), Int(CC_SHA512_DIGEST_LENGTH), signature, &signatureLength)
        if status != noErr
        {
            print(«Signature fail: \(status)»)
        }
         
        return Data.init(bytes: signature, count: signatureLength) //resize to actual signature size
    }
     
    func verifySignature(plainText: Data, signature: Data) -> Bool
    {
        guard recipient?.publicKey != nil else
        {
            print(«Recipient public key unavailable»)
            return false
        }
         
        let digestToVerify = self.sha512Digest(forData: plainText)
        let signedHashBytesSize = signature.count
         
        let status = SecKeyRawVerify(recipient!.publicKey!, .PKCS1SHA512, [UInt8](digestToVerify), Int(CC_SHA512_DIGEST_LENGTH), [UInt8](signature as Data), signedHashBytesSize)
        return status == noErr
    }
}

Это проверяет сообщение, а также «идентификацию» конкретного пользователя, поскольку только этот пользователь обладает своим закрытым ключом.

Это не означает, что мы связываем ключ с тем, кем является пользователь в реальной жизни — проблема сопоставления открытого ключа с конкретным пользователем — это другой домен. Хотя решения выходят за рамки этого руководства, популярные приложения для безопасного чата, такие как Signal и Telegram, позволяют пользователям проверять отпечаток пальца или номер через дополнительный канал связи. Точно так же Pidgin предлагает схему вопросов и ответов, в которой вы задаете вопрос, который должен знать только пользователь. Эти решения открывают целый мир споров о том, каким должен быть лучший подход.

Тем не менее, наше криптографическое решение действительно подтверждает, что сообщение может быть отправлено только тем, у кого есть определенный закрытый ключ.

Давайте запустим простой тест нашего примера:

01
02
03
04
05
06
07
08
09
10
11
12
13
if #available(iOS 9.0, *)
{
    let alice = User.init(withUserID: «aaaaaa1»)
    let bob = User.init(withUserID: «aaaaaa2»)
     
    let accepted = alice.initiateConversation(withUser: bob)
    if (accepted)
    {
        alice.sendMessage(«Hello there»)
        bob.sendMessage(«Test message»)
        alice.sendMessage(«Another test message»)
    }
}

Часто при работе со сторонними сервисами вы заметите другие высокоуровневые термины, используемые для аутентификации, такие как OAuth и SSO. Хотя этот урок посвящен созданию подписи, я кратко объясню, что означают другие термины.

OAuth — это протокол для аутентификации и авторизации. Он выступает в качестве посредника при использовании чужой учетной записи для сторонних услуг и направлен на решение проблемы выборочного разрешения доступа к вашим данным. Если вы входите в службу X через Facebook, на экране появляется запрос, например, разрешено ли службе X получать доступ к вашим фотографиям в Facebook. Это достигается путем предоставления токена без раскрытия пароля пользователя.

Единая регистрация, или SSO, описывает поток, в котором аутентифицированный пользователь может использовать свои учетные данные для доступа к нескольким сервисам. Примером этого является то, как ваша учетная запись Gmail работает для входа на YouTube. Если у вас было несколько разных сервисов в вашей компании, вы можете не захотеть создавать отдельные учетные записи пользователей для всех разных сервисов.

В этом уроке вы увидели, как создавать подписи, используя самые популярные стандарты. Теперь, когда мы рассмотрели все основные концепции, давайте подведем итоги!

  • Используйте HMAC, когда вам нужна скорость и вы уверены, что секретный ключ можно безопасно обменять.
  • Если ключи должны путешествовать по сети, лучше использовать RSA или ECDSA.
  • RSA по-прежнему является самым популярным стандартом. Этап проверки довольно быстрый. Используйте RSA, если остальная часть вашей команды уже знакома со стандартом или использует его.
  • Если вам нужно постоянно генерировать ключи на медленном устройстве, используйте ECDSA. Хотя проверка ECDSA немного медленнее, чем проверка RSA, это не сравнимо с тем количеством секунд, сэкономленных по RSA для генерации ключа.

Вот и все, что касается цифровых подписей в Swift. Если у вас есть какие-либо вопросы, не стесняйтесь, напишите мне в разделе комментариев, а тем временем ознакомьтесь с некоторыми другими нашими учебниками по безопасности данных и разработке приложений в Swift!

  • iOS SDK
    Защита данных iOS в состоянии покоя: защита данных пользователя
    Коллин Штюрт
  • iOS SDK
    Защита данных iOS в покое: брелок
    Коллин Штюрт
  • стриж
    Что нового в Swift 4
    Патрик Балестра
  • iOS SDK
    Ускоренный вход в систему с паролем автозаполнения в iOS 11
    Патрик Балестра