SV.HASH.NO_SALT

ソルトなしの一方向暗号化ハッシュの使用

一方向暗号化ハッシュ機能がソルトなしで 1 個の入力データに適用された場合に、このエラーが報告されています。

脆弱性とリスク

ソフトウェアがユーザーのパスワードについて計算した一方向暗号化ハッシュを保存している場合、一意のソルトを各パスワードの前またま後に追加することをお勧めします。2 人のユーザーが同じパスワードを持っていても、ハッシュは異なることになります。

攻撃者がハッシュにアクセスする場合、ソルトが追加されていなければ複数のユーザーパスワードを推測しやすくなります。

脆弱コード例

コピー
  /*
   * Adds a new user; stores login and password hash.
   */
   public void addUser(String login, byte[] password) {
     try {   
        MessageDigest md = MessageDigest.getInstance("MD5");           
        
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(password); // < -- calculate hash for the password
                    
                    
                    
                    
                    
                    
                    
                    
                    
        byte[] hash = md.digest();
        storeHashString(login, Hex.encodeHexString(hash));
    } catch (NoSuchAlgorithmException e) { 
       throw new IllegalStateException(e)
    }
  }    

  /*
   * Validates user login information.Returns true if user exists, and
     entered password hash matches stored password hash;
   * otherwise returns false.     
   */
   public boolean login(String login, byte[] password) {
     try {
        String storedHash = readHashString(login);
        if (storedHash != null) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(password); // < -- calculate hash for the password
                    
                    
                    
                    
                    
                    
                    
                    
                    
            byte[] hash = md.digest();
            return MessageDigest.isEqual(hash, Hex.decodeHex(storedHash.toCharArray()));
          } else {
             return false;
          }
        } catch (NoSuchAlgorithmException | DecoderException e) {
           throw new IllegalStateException(e);
        }
      }
 
 .。。

        hashManager.addUser("Alice", "changeit".getBytes());
        hashManager.addUser("Bob", "changeit".getBytes());

この例では、ユーザー Alice、Bob は同じパスワード「changeit」を持ち、両者の保存されているハッシュは「b91cd1a54781790beaa2baf741fa6789」です。従って攻撃者がパスワードハッシュテーブルを盗み出せば、辞書を使用して容易に「b91cd1a54781790beaa2baf741fa6789」を「changeit」にリバースできます。

修正コード例

コピー
 /*
  * Adds a new user; stores login and "salt:hash".
  */
  public void addUser(String login, byte[] password) {
   try {
     byte[] salt = new byte[16];
     secureRandom.nextBytes(salt);
     MessageDigest md = MessageDigest.getInstance("MD5");
     
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(salt); // use random salt
                    
                    
                    
                    
                    
                    
                    
                    
                    
    
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(password); // and password
                    
                    
                    
                    
                    
                    
                    
                    
                    
    byte[] hash = md.digest();
    storeHashString(login, Hex.encodeHexString(salt) + ":"+ Hex.encodeHexString(hash));
  } catch (NoSuchAlgorithmException e) {
     throw new IllegalStateException(e);
  }
 }

 /*
  * Validates user login information.ユーザーが存在し、かつ入力されたパスワードが格納されている "salt:hash" と一致する場合は true を返し、そうでない場合は false を返します。
  */
  public boolean login(String login, byte[] password) {
    try {
      String storedSaltAndHash = readHashString(login);
      if (storedSaltAndHash != null) {
          String[] saltAndHash = storedSaltAndHash.split(":");
          if (saltAndHash.length != 2) {
              throw new IllegalStateException("Expected salt:hash string");
          }
          MessageDigest md = MessageDigest.getInstance("MD5");
          
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(Hex.decodeHex(saltAndHash[0].toCharArray())); // use stored salt
                    
                    
                    
                    
                    
                    
                    
                    
                                   
          
                    
                    
                    
                    
                    
                    
                    
                    
                    md.update(password);
                    
                    
                    
                    
                    
                    
                    
                    
                    
          byte[] hash = md.digest();
          return MessageDigest.isEqual(hash, Hex.decodeHex(saltAndHash[1].toCharArray()));
       } else {
         return false;
       }
     } catch (NoSuchAlgorithmException | DecoderException e) {
        throw new IllegalStateException(e);
     }
   } 

 .。。

  hashManager.addUser("Alice", "changeit".getBytes());
  hashManager.addUser("Bob", "changeit".getBytes());
この例では、Alice と Bob のパスワードハッシュレコードはランダム化されています。以下のようになります。
Alice -> 03c016ca60ee8c53aa8f24301a08ec27:de88b6fc874d83eb30aa9020f431bde3 
Bob -> e2eafef8be75cd58b7ed598ddb37e128:4bc3b11a2bf5854b66543f8ba9ffa2c6

関連チェッカー

セキュリティトレーニング

Secure Code Warrior が提供しているアプリケーションセキュリティトレーニング教材。