web-dev-qa-db-ja.com

単純な文字列比較はタイミング攻撃に対して安全ではない

PHPで適切に暗号化する方法は? のコメントで学んだように、PHPで次のような文字列比較を使用すると、タイミング攻撃の影響を受けやすくなります。したがって、2つのMACまたはハッシュ(パスワードハッシュも)が等しいかどうかの比較には使用しないでください。

if ($hash1 === $hash2) {
   //mac verification is OK
   echo "hashs are equal"
} else {
  //something bad happenend
  echo "hashs verification failed!";
}

誰かが私に問題が正確に何であるか、攻撃がどのように見えるか、そしておそらくこの特定の問題を回避する安全な解決策を提供することを詳しく教えてもらえますか?それはどのように正しく行われるべきですか?これはPHP=の特定の問題ですか、それともPython、Java、C++、Cなどの他の言語でも同じ問題がありますか?

17
evildead

さまざまな言語の時定数関数のリストを追加します。

[〜#〜] php [〜#〜]

ディスカッション: https://wiki.php.net/rfc/timing_attack

bool hash_equals ( string $known_string , string $user_string )

http://php.net/manual/en/function.hash-equals.php

Javaディスカッション: http://codahale.com/a-lesson-in-timing-attacks/

public static boolean  MessageDigest.isEqual(byte[] digesta, byte[] digestb)

http://docs.Oracle.com/javase/7/docs/api/Java/security/MessageDigest.html#isEqual(byte []、%20byte []) =

C/C++ディスカッション: https://cryptocoding.net/index.php/Coding_rules

int util_cmp_const(const void * a, const void *b, const size_t size) 
{
  const unsigned char *_a = (const unsigned char *) a;
  const unsigned char *_b = (const unsigned char *) b;
  unsigned char result = 0;
  size_t i;

  for (i = 0; i < size; i++) {
    result |= _a[i] ^ _b[i];
  }

  return result; /* returns 0 if equal, nonzero otherwise */
}

もっと私はここで見つけました: http://www.levigross.com/2014/02/07/constant-time-comparison-functions-in-python-haskell-clojure-Java-etc/

Python(2.x):

#Taken from Django Source Code

def constant_time_compare(val1, val2):
    """
    Returns True if the two strings are equal, False otherwise.

    The time taken is independent of the number of characters that match.

    For the sake of simplicity, this function executes in constant time only
    when the two strings have the same length. It short-circuits when they
    have different lengths.
    """
    if len(val1) != len(val2):
        return False
    result = 0
    for x, y in Zip(val1, val2):
        result |= ord(x) ^ ord(y)
    return result == 0

Python 3.x

#This is included within the stdlib in Py3k for an C alternative for Python 2.7.x see https://github.com/levigross/constant_time_compare/
from operator import _compare_digest as constant_time_compare

# Or you can use this function taken from Django Source Code

def constant_time_compare(val1, val2):
    """
    Returns True if the two strings are equal, False otherwise.

    The time taken is independent of the number of characters that match.

    For the sake of simplicity, this function executes in constant time only
    when the two strings have the same length. It short-circuits when they
    have different lengths.
    """
    if len(val1) != len(val2):
        return False
    result = 0
    for x, y in Zip(val1, val2):
        result |= x ^ y
    return result == 0

ハスケル

import Data.Bits
import Data.Char
import Data.List
import Data.Function

-- Thank you Yan for this snippet 

constantTimeCompare a b =
  ((==) `on` length) a b && 0 == (foldl1 (.|.) joined)
  where
    joined = zipWith (xor `on` ord) a b

ルビー

def secure_compare(a, b)
     return false if a.empty? || b.empty? || a.bytesize != b.bytesize
     l = a.unpack "C#{a.bytesize}"

     res = 0
     b.each_byte { |byte| res |= byte ^ l.shift }
     res == 0
   end

Java(一般)

// Taken from http://codahale.com/a-lesson-in-timing-attacks/
public static boolean isEqual(byte[] a, byte[] b) {
    if (a.length != b.length) {
        return false;
    }

    int result = 0;
    for (int i = 0; i < a.length; i++) {
      result |= a[i] ^ b[i]
    }
    return result == 0;
}
20
evildead

ここでの問題は、一般的な文字列比較関数が文字列の違いを見つけるとすぐに戻ることです。最初のバイトが異なる場合、2つの文字列の1バイトを調べただけで戻ります。唯一の違いが最後のバイトにある場合、返される前に両方の文字列全体を処理します。これは一般的に物事をスピードアップし、それは通常良いことです。しかし、それはまた、文字列を比較するのにかかる時間を知ることができる誰かが最初の違いがどこにあるかを推測できることを意味します。

攻撃シナリオでは、攻撃者は$mac1(攻撃者が作成したメッセージから取得)を完全に制御できますが、$mac2は攻撃者のメッセージの実際の有効なMACです。 $mac2は攻撃者から秘密にしておく必要があります。そうしないと、攻撃者がメッセージに貼り付けて有効なメッセージを偽造できます。攻撃者は、応答を取得するのにかかる時間を分析することにより、おそらく最初の違いが自分のMACと実際のMACのどこにあるかを理解できます。彼はその1バイトのすべての可能性を試し、正しいバイトを見つけて、最初のkバイトが正しいという知識で安全な次のバイトに取り組むことができます。最後に、彼は試さなければならなかった256 ^ lenではなく、256 * len MAC(lenがMACの長さの場合)だけを試しました。

18
cpast

文字列比較に対するタイミング攻撃は、PHP固有ではありません。これらは、標準の「短絡」比較アルゴリズムを使用して、ユーザー指定の文字列が秘密の文字列に対してチェックされるすべてのコンテキストで機能します(一致しない最初のバイトでチェックが停止します)。これは、PHP、Python、C、さらにはMySQLのようなデータベースシステムにも当てはまります。

この問題に対する標準的なアプローチは、内容に関係なく、常に文字列のすべてのバイトを反復処理することです。擬似コードとして:

function safe_string_comp(str_1, str_2):
    if byte_length(str_1) =/= byte_length(str_2):
        return FALSE
    else:
        comparison_bit := 0  // 0 if the strings match, 1 otherwise
        for i := 0, i < byte_length(str_1), i := i + 1:
           comparison_bit := comparison_bit | (str_1[i] ^ str_2[i])

        return comparison_bit == 0

象徴 |はビット単位のOR演算子を示し、^はビット単位のXORです。

最近のPHPバージョン(> = 5.6.0)には、すでに hash_equals 。利用できない場合は、上記のアルゴリズムを実装する必要があります。したがって、タイミングセーフな文字列比較関数は次のようになります。

<?php

/**
 * Count the number of bytes in a string.
 *
 * Note that the strlen() function is ambiguous, because it will either return the number of *bytes* or the
 * number of *characters* with regard to mb_internal_encoding(), depending on whether the Mbstring extension
 * has overloaded the string functions:
 * http://php.net/manual/en/mbstring.overload.php
 *
 * For example, the non-overloaded strlen() function returns 2 for the string "\xC3\x84". However, if the
 * function is overloaded and the internal encoding set to UTF-8, the same string is interpreted as a single
 * character, namely the "Ä" umlaut. So the function returns 1 in this case.
 */
function byte_length($binary_string)
{
    if (extension_loaded('mbstring'))
        return mb_strlen($binary_string, '8bit');
    else
        return strlen($binary_string);
}



/**
 * Timing-safe string comparison.
 *
 * The standard string comparison algorithm stops as soon as it finds a non-matching byte. This leaks information
 * about the string contents through time differences, because the longer the common prefix, the longer the
 * comparison takes (e. g. checking "aaax" against "aaaa" theoretically requires slightly more time than checking
 * "xaaa" against "aaaa").

 * To avoid this problem in security contexts like MAC verification, iterate over *all* bytes of the strings
 * regardless of the content.
 */
function secure_string_equals($string_1, $string_2)
{
    // Use built-in hash_equals() function if available (PHP >= 5.6.0)
    if (function_exists('hash_equals'))
    {
        return hash_equals($string_1, $string_2);
    }
    else
    {
        $equals = false;

        if (!is_string($string_1) || !is_string($string_2))
        {
            trigger_error('One of the arguments is not a string.', E_USER_ERROR);
        }

        if (byte_length($string_1) == byte_length($string_2))
        {
            // 0 if the strings are equal, 1 otherwise
            $comparison_bit = 0;
            for ($byte_index = 0; $byte_index < byte_length($string_1); $byte_index++)
            {
                $comparison_bit |= ord($string_1[$byte_index]) ^ ord($string_2[$byte_index]);
            }

            $equals = ($comparison_bit == 0);
        }

        return $equals;
    }
}
3
Fleche