Реализация клиента SCRAM-SHA1, где-то неправильно

Примечание: я уже прочитал очень хороший ответ на этот вопрос, но он не отвечает на мои вопросы.

Я пытаюсь реализовать стандарт аутентификации SCRAM-SHA1, как указано в RFC 5802, в Common Lisp. Я сталкиваюсь с проблемами, когда дело доходит до генерации окончательного ответного сообщения клиента.

Это код функции (остальные функции доступны здесь) - это попытка реализовать алгоритм, как описано на странице 7 RFC:

(defun gen-client-final-message
    (&key password client-nonce client-initial-message server-response)
  (check-type client-nonce string)
  (check-type client-initial-message string)
  (check-type server-response string)
  (check-type password string)
  "Takes a password, the initial client nonce, the initial client message & the server response.
   Generates the final client message, and returns it along with the server signature."
  (progn
    (if (eq nil (parse-server-nonce :nonce client-nonce :response server-response)) NIL)
    (let* ((final-message-bare (format nil "c=biws,r=~a" (parse-server-nonce :nonce client-nonce
                                                                             :response server-response)))
           (salted-password    (ironclad:pbkdf2-hash-password
                                 (ironclad:ascii-string-to-byte-array password)
                                 :salt       (ironclad:ascii-string-to-byte-array
                                               (parse-server-salt :response server-response))
                                 :digest     :sha1
                                 :iterations (parse-server-iterations :response server-response)))
           (client-key         (gen-hmac-digest :key salted-password
                                                :message (ironclad:ascii-string-to-byte-array "Client Key")))
           (stored-key         (gen-sha1-digest :key client-key))
           (auth-message       (format nil "~a,~a,~a"
                                       client-initial-message
                                       server-response
                                       final-message-bare))
           (client-signature   (gen-hmac-digest :key stored-key
                                                :message (ironclad:ascii-string-to-byte-array auth-message)))
           (client-proof       (integer->bit-vector (logxor (ironclad:octets-to-integer client-key)
                                                            (ironclad:octets-to-integer client-signature))))
           (server-key         (gen-hmac-digest :key salted-password
                                                :message (ironclad:ascii-string-to-byte-array "Server Key")))
           (server-signature   (gen-hmac-digest :key server-key
                                                :message (ironclad:ascii-string-to-byte-array auth-message)))
           (final-message      (format nil "~a,p=~a"
                                       final-message-bare
                                       (base64-encode (write-to-string client-proof)))))
      (pairlis '(final-message
                 final-message-bare
                 salted-password
                 client-key
                 stored-key
                 auth-message
                 client-signature
                 client-proof
                 server-key
                 server-signature)
               (list final-message
                     final-message-bare
                     salted-password
                     client-key
                     stored-key
                     auth-message
                     client-signature
                     client-proof
                     server-key
                     server-signature)))))

В примере разговора в RFC используется имя пользователя user и пароль pencil:

C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
S: r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,
   i=4096
C: c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,
   p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=
S: v=rmF9pqV8S7suAoZWja4dJRkFsKQ=

Принимая тот же ответ сервера (r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096) и кормя его в свою функцию, я получаю:

* (cl-scram:gen-client-final-message :password "pencil" :client-nonce "fyko+d2lbbFgONRv9qkxdawL" :client-initial-message "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL" :server-response "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096")

((CL-SCRAM::SERVER-SIGNATURE
  . #(33 115 21 228 67 190 35 238 223 122 117 125 222 242 209 136 175 228 67
      151))
 (CL-SCRAM::SERVER-KEY
  . #(15 224 146 88 179 172 133 43 165 2 204 98 186 144 62 170 205 191 125 49))
 (CL-SCRAM::CLIENT-PROOF
  . #*1100100111101011000000111010100000010101011001000101011100110001111100001100100010001101001000110101001010101010001011111000100011100001001110100001001110000)
 (CL-SCRAM::CLIENT-SIGNATURE
  . #(251 9 164 14 244 111 236 112 227 116 148 143 243 255 231 75 58 114 21
      88))
 (CL-SCRAM::AUTH-MESSAGE
  . "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j")
 (CL-SCRAM::STORED-KEY
  . #(233 217 70 96 195 157 101 195 143 186 217 28 53 143 20 218 14 239 43
      214))
 (CL-SCRAM::CLIENT-KEY
  . #(226 52 196 123 246 195 102 150 221 109 133 43 153 170 162 186 38 85 87
      40))
 (CL-SCRAM::SALTED-PASSWORD
  . #(29 150 238 58 82 155 90 95 158 71 192 31 34 154 44 184 166 225 95 125))
 (CL-SCRAM::FINAL-MESSAGE-BARE
  . "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j")
 (CL-SCRAM::FINAL-MESSAGE
  . "c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=IyoxMTAwMTAwMTExMTAxMDExMDAwMDAwMTExMDEwMTAwMDAwMDEwMTAxMDExMDAxMDAwMTAxMDExMTAwMTEwMDAxMTExMTAwMDAxMTAwMTAwMDEwMDAxMTAxMDAxMDAwMTEwMTAxMDAxMDEwMTAxMDEwMDAxMDExMTExMDAwMTAwMDExMTAwMDAxMDAxMTEwMTAwMDAxMDAxMTEwMDAw"))

Как видите, мой client-proof (p= часть final-message) сильно отличается от того, что в примере.

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

1 ответ

Решение

Промежуточные значения для образца в RFC 5802: Механизмы аутентификации ответа на соленый вызов (SCRAM) SASL и GSS-API находятся в нижней части этого ответа.


Ваш p ценность слишком длинная; вы, вероятно, кодируете биты как строки, а не байты. Вы должны перебрать блоки байтов и XOR каждый неподписанный байт отдельно. Преобразование в целое число, затем в строку битов, а затем обратно в строку октетов завершится неудачей, поскольку, вероятно, удалит наиболее значимые нулевые биты. Получив строку октетов XOR, вы можете кодировать ее с помощью 64-го кода.


Кроме того, вам нужно удалить n,, с самого начала вашего AuthMessage, как указано в RFC.


Для будущих разработчиков, без лишних слов, промежуточные значения:

В базе 64:

SaltedPassword: HZbuOlKbWl+eR8AfIposuKbhX30=
ClientKey: 4jTEe/bDZpbdbYUrmaqiuiZVVyg=
StoredKey: 6dlGYMOdZcOPutkcNY8U2g7vK9Y=
AuthMessage: n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
ClientSignature: XXE4xIawv6vfSePi2ovW5cedthM=
ClientProof: v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=

Использование десятичных массивов:

SaltedPassword: 29 150 238 58 82 155 90 95 158 71 192 31 34 154 44 184 166 225 95 125
ClientKey: 226 52 196 123 246 195 102 150 221 109 133 43 153 170 162 186 38 85 87 40
StoredKey: 233 217 70 96 195 157 101 195 143 186 217 28 53 143 20 218 14 239 43 214
AuthMessage: n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j
ClientSignature: 93 113 56 196 134 176 191 171 223 73 227 226 218 139 214 229 199 157 182 19
ClientProof: 191 69 252 191 112 115 217 61 2 36 102 201 67 33 116 95 225 200 225 59
Другие вопросы по тегам