Categories
Dokumentasi

General Purpose Hashing vs Password Hashing

Salah satu kunci keamanan data adalah kerahasiaan, integritas dan penyandian (kriptografi). Salah satu cara untuk memeriksa integritas adalah dengan hash. Hash merupakan representasi data ke dalam string dengan jumlah karakter tetap, misalnya md5 memiliki panjang string 32

Hash sendiri ada yang bersifat umum (general purpose) dan ada yang didesain khusus untuk password agar tidak mudah dibobol. Dalam konteks pesan, hasil dari hash tidak dapat dikembalikan ke pesan asli, sedangkan penyandian (enkripsi) dapat dikembalikan ke pesan asli (dekripsi).

Daftar Isi

General Purpose vs Password Hashing

Apakah perbedaan General Purpose Hashing, Password Hashing dan Enkripsi? Apa pula itu Plain Text, Salt, Pepper, Entropy? Apa tips berkaitan dengan password?

— General Purpose Hashing vs Password Hashing
https://bptsi.unisayogya.ac.id/general-purpose-hashing-vs-password-hashing/ 2021-03-04 13:21:34

Istilah

|-Plain Text

Plain text kurang lebih adalah pesan atau teks asli, baik dapat dimengerti maupun tidak.

Contoh: Hallo, $%abc, dan sebagainya.

|-Enkripsi vs Hash

Enkripsi kurang lebih adalah proses menyandikan plain text agar aman ketika dikirim dan dapat diubah ke dalam plain text ketika sampai di tujuan pengiriman melalui proses yang dinamakan dengan dekripsi. Proses enkripsi dan dekripsi membutuhkan teks sebagai kunci.

Contoh:
Plain text → Hallo
Kunci → 123
Enkripsi → enkrip(Hallo, 123) → G5HT7opFNsg=
Dekripsi→ enkrip(G5HT7opFNsg=, 123) → Hallo

Hash kurang lebih adalah proses penyandian plain text satu arah sedemikian sehingga sandi tidak dapat dikembalikan lagi menjadi plain text.

|-Entropi

Entropi kurang lebih adalah seberapa banyak variasi di dalam password.

Ketika password menggunakan huruf kecil, maka hanya ada 26 karakter untuk variasi. Alfa numerik lebih baik karena terdapat 36 karakter. Namun, akan lebih baik lagi apabila mengkombinasikan huruf kapital, huruf kecil, angka dan simbol yang akan menjadi 96 karakter.

|-Salt, Pepper

Salt kurang lebih adalah tambahan teks dalam penyandian, biasanya digabungkan dengan plain text sebelum disandikan. Apabila terdapat pepper, maka salt berada di tempat yang sama dengan password yang sudah disandikan berada.

Pepper kurang lebih adalah sama dengan salt, tetapi diletakkan di tempat yang berbeda dengan password yang sudah disandikan berada. Pepper berguna untuk mempersulit pembobol membongkar password karena ada teks tambahan yang tidak ada di dalam database bersama dengan password. Baca: https://security.stackexchange.com/a/3289

Contoh:
Plain text → Hallo
Salt → 123 → diletakkan di tabel di database
Pepper → 456 → misalnya diletakkan di config.php
Sandi → hash(Hallo123456) → disimpan di tabel di database

|-General Purpose Hashing

General Purpose Hashing kurang lebih adalah algoritma hash yang didesain untuk dapat memberikan hash pada berkas yang besar dan prosesnya terjadi dengan cepat. Biasanya digunakan untuk checksum atau memeriksa integritas dari teks atau berkas. Algoritma general purpose hashing antara lain md5, sha256, dan sebagainya.

|-Password Hashing

Password Hashing kurang lebih adalah algoritma hash yang didesain khusus untuk password dan tidak cepat, sehingga membuat pembobol membutuhkan waktu dan dana yang sangat banyak untuk mencoba memecahkan plain text dari password tersebut. Algoritma general purpose hashing antara lain argon2, scrypt, bcrypt, dan sebagainya.

Tips Password

  1. Gunakan password hashing + salt + pepper untuk menyimpan password. Hanya gunakan general purpose hashing untuk melakukan hash terhadap salt atau pepper.
  2. General purposes hashing juga dapat digunakan sebagai plain text untuk password hashing. Apabila mengkombinasikan beberapa hash, maka output dari hash sebelumnya perlu di-base64_encode atau di-hex, termasuk untuk salt atau pepper. Baca: https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html
  3. Sebaiknya password disimpan di dalam kolom berupa varchar(255). Baca: https://www.php.net/manual/en/function.password-hash.php
  4. Jangan pernah mencatat (log) password, baik sengaja maupun tidak. Tidak sengaja contohnya:
    1. mengirim password dengan metode GET yang bisa jadi akan masuk ke dalam history dari browser
    2. query dengan plain text yang bisa masuk ke slow log database atau log lainnya
  5. Implementasikan minimal 8-10 karakter untuk password dengan huruf kapital, huruf kecil, angka dan simbol. Jangan batasi maksimal karakter yang akan digunakan.
  6. Hati-hati ketika mengimplementasikan fungsi hash, perhatikan encoding karakter yang digunakan, apakah UTF-8, ISO-8859-1 atau encoding lain? PHP secara default menggunakan UTF-8.

Contoh Implementasi Password

|-Persiapan

  1. Buat tabel yang minimal terdapat salt dan algoritma yang digunakan untuk melakukan hash
  2. Buat fungsi untuk membuat random salt. Gunakan fungsi tersebut untuk membuat salt. Pepper dikirim dari aplikasi, misalnya web.
  3. Buat fungsi untuk membuat hash dari password
  4. Buat fungsi untuk cek password. Gunakan fungsi tersebut untuk memvalidasi password dan apabila terdapat algoritma baru untuk hash password, maka salt dan password di update

Buat tabel contoh:

USE `test`;
CREATE TABLE `user` (
  `iduser` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `password` varchar(255) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  `algorithm` tinyint(3) unsigned DEFAULT '1',
  PRIMARY KEY (`iduser`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

|–Kelas Password (PHP)

Buat skrip untuk menangani password:

<?php
class table_user
{
  $saltFieldname       = "salt";
  $passwordFieldname   = "password";
  $algorithmFieldname  = "algorithm";
  $userTablename       = "user";
  $iduserFieldname     = "iduser";
  
  function table_user()
  {
  }
  
  function set_table_user($saltFieldname, $passwordFieldname, $algorithmFieldname, $userTablename, $iduserFieldname)
  {
   $this->saltFieldname       = $saltFieldname;
   $this->passwordFieldname   = $passwordFieldname;
   $this->algorithmFieldname  = $algorithmFieldname;
   $this->userTablename       = $userTablename;
   $this->iduserFieldname     = $iduserFieldname;
  }
}

class pass_sec
{
 private $salt_;
 private $pepper_;
 private $latestalgorithm_;
 private $conn_;
 private $tuser = new table_user();
 
 function pass_sec()
 {
  //----- must be change once -----//
  $this->pepper_          = 'MGVhYTI3MDEwZW=';
  //----- +1 if new algorithm added -----//
  $this->latestalgorithm_ = 1;
 }
 
 function hash_password($plaintext_, $salt_, $algorithm_)
 {
  switch ($algorithm_)
  {
   //----- write new algorithm here -----//
   case 2:
   default:
   //----- change with your current algorithm -----//
   //----- maybe return plaintext_; -----//
   //----- or return hash('md5', $plaintext_); -----//
    return hash('sha512', $plaintext_ . $salt_ . $this->pepper_);
  }
 }
 
 function verify_password($plaintext_, $salt_, $algorithm_, $password_)
 {
  switch ($algorithm_)
  {
   //----- write how to verify new algorithm here -----//
   case 2:
   default:
   //----- change with your current verification algorithm -----//
    return $this->hash_password($plaintext_, $salt_, $algorithm_) == $password_;
  }
 }

 function safe_random_salt()
 {
  return substr(base64_encode( hash('sha512', uniqid()) ),0,255);
 }
 
 function password_check($iduser_, $plaintext_, $fieldtocheck_="", $checkedvalues_comma_="")
 {
  $row      = $this->db_get_password($iduser_, $fieldtocheck_);
  if (trim($checkedvalues_comma_) != "" && trim($fieldtocheck_) != "")
  {
    $allowed = 0;
    $check_  = explode(",", $checkedvalues_comma_);
    foreach ($check_ as $rowcheck_)
    {
      if (trim($rowcheck_) == trim($row[$fieldtocheck_]))
      {
        $allowed = 1;
        break;
      }
    }
  }
  else
  {
    $allowed = 1;
  }
  if (!$allowed)
  {
   return 0;
  }
  if ( $this->verify_password($plaintext_, $row['salt'], $row['algorithm'],$row['password']) )
  {
   if ( $row['algorithm'] < $this->latestalgorithm_ )
   {
    $salt_ = $this->safe_random_salt();
    $this->db_update_password($iduser_, $plaintext_, $salt_);
   }
   return true;
  }
  else
  {
   return false;
  }
 }
 
 function bcrypt_test()
 {
  //https://www.php.net/manual/en/function.password-hash.php
  $timeTarget = 0.05; // 50 milliseconds 
  $cost = 8;
  do 
  {
   $cost++;
   $start = microtime(true);
   password_hash("test", PASSWORD_BCRYPT, ["cost" => $cost]);
   $end = microtime(true);
  } 
  while (($end - $start) < $timeTarget);

  echo "Appropriate Cost Found: " . $cost;
 }
 
 function db_connect($servername, $username, $password, $dbname)
 {
  $this->conn_ = new mysqli($servername, $username, $password, $dbname);
  $this->conn_->options(MYSQLI_OPT_SSL_VERIFY_SERVER_CERT, true);
  $this->conn_->ssl_set('/etc/mysql/ssl/client-key.pem', '/etc/mysql/ssl/client-cert.pem', '/etc/mysql/ssl/ca-cert.pem', NULL, NULL);
  $this->conn_->real_connect($servername, $username, $password, $dbname, 3306, NULL, MYSQLI_CLIENT_SSL);
 }
 
 function db_get_password($iduser_, $fieldtocheck_ = "")
 {
  if (!$this->conn_->connect_error)
  {
   $stmt   = $this->conn_->prepare("select " . $tuser->saltFieldname . ", " . $tuser->passwordFieldname . ", " . $tuser->algorithmFieldname . (empty($fieldtocheck_)?"":",".$fieldtocheck_) . " from " . $tuser->userTablename . " where " . $tuser->iduserFieldname . "=? limit 0,1");
   $stmt->bind_param("i", $iduser_);
   $stmt->execute();
   $result = $stmt->get_result();
   while ($row = $result->fetch_assoc())
   {
    if (method_exists($stmt,'close'))
    {
     $stmt->close();
    }
    return $row;
   }
  }
 }
 
 function db_update_password($iduser_, $plaintext_, $salt_)
 {
  if (!$this->conn_->connect_error)
  {
   $stmt = $this->conn_->prepare("update " . $tuser->userTablename . " set " . $tuser->saltFieldname . "=?, " . $tuser->algorithmFieldname . "=?, " . $tuser->passwordFieldname . "=? where " . $tuser->iduserFieldname . "=?");
   $newpass = $this->hash_password($plaintext_, $salt_, $this->latestalgorithm_);
   $stmt->bind_param("sisi", $salt_, $this->latestalgorithm_, $newpass, $iduser_);
   $stmt->execute();
   if (method_exists($stmt,'close'))
   {
    $stmt->close();
   }   
  }
 }
 
 function db_simulate_insert($ids_)
 {
  foreach ($ids_ as $id)
  {
   $salt_ = $this->safe_random_salt();
   $stmt  = $this->conn_->prepare("insert into " . $tuser->userTablename . " (" . $tuser->iduserFieldname . ", " . $tuser->passwordFieldname . ", " . $tuser->saltFieldname . ", " . $tuser->algorithmFieldname . ") values (?, ?, ?, ?) on duplicate key update " . $tuser->iduserFieldname . "=" . $tuser->iduserFieldname . "");
   $pass  = $this->hash_password('this is a password', $salt_, $this->latestalgorithm_);
   $stmt->bind_param("issi", $id, $pass, $salt_, $this->latestalgorithm_);
   $stmt->execute();
  }
  if (method_exists($stmt,'close'))
  {
   $stmt->close();
  }
 }
 
 functin set_tuser($saltFieldname, $passwordFieldname, $algorithmFieldname, $userTablename, $iduserFieldname) 
 {
  $this->tuser->set_table_user($saltFieldname, $passwordFieldname, $algorithmFieldname, $userTablename, $iduserFieldname);
 }
}

|– Kelas Password (Java)

Buat skrip untuk menangani password:

import java.math.BigInteger;
import java.security.MessageDigest;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Base64;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

class table_user
{
  public String saltFieldname       = "salt";
  public String passwordFieldname   = "password";
  public String algorithmFieldname  = "algorithm";
  public String userTablename       = "user";
  public String iduserFieldname     = "iduser";
  
  public table_user()
  {
  }
  
  public void set_table_user(String saltFieldname, String passwordFieldname, String algorithmFieldname, String userTablename, String iduserFieldname)
  {
   this.saltFieldname       = saltFieldname;
   this.passwordFieldname   = passwordFieldname;
   this.algorithmFieldname  = algorithmFieldname;
   this.userTablename       = userTablename;
   this.iduserFieldname     = iduserFieldname;
  }
}

public class pass_sec 
{
 private static final int SALT_COLUMN            = 0;
 private static final int PASSWORD_COLUMN        = 1;
 private static final int ALGORITHM_COLUMN       = 2;
 
 private String salt_;
 private final String pepper_;
 private final int latestalgorithm_;
 private Connection conn_;
 @SuppressWarnings("FieldMayBeFinal")
 private table_user tuser = new table_user();

 public pass_sec()
 {
  //----- must be change once -----//
  this.pepper_          = "MGVhYTI3MDEwZW=";
  //----- +1 if new algorithm added -----//
  this.latestalgorithm_ = 1;
 }
 
 @SuppressWarnings("UseSpecificCatch")
 String hash(String algorithm_, String plaintext_)
 {
  try
  {
   byte[] hashBytes;   
   MessageDigest md = MessageDigest.getInstance(algorithm_);
   md.reset();
   //gunakan UTF-8 karena PHP menggunakan UTF-8 secara default
   hashBytes = md.digest(plaintext_.getBytes("UTF-8"));
   BigInteger no = new BigInteger(1, hashBytes);
   String hashtext = no.toString(16);
   while (hashtext.length() < 32)
   { 
    hashtext = "0" + hashtext; 
   }
   return hashtext;
  }
  catch (Exception ex) 
  {
   Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
  }
  return "";
 }

 public String hash_password(String plaintext_, String salt_, int algorithm_)
 {
  switch (algorithm_)
  {
   //----- write new algorithm here -----//
   case 2:
   default:
   //----- change with your current algorithm -----//
   //----- maybe return plaintext_; -----//
   //----- or return hash("md5", plaintext_); -----//
    return hash("SHA-512", plaintext_ + salt_ + this.pepper_);
  }
 }

 public boolean verify_password(String plaintext_, String salt_, int algorithm_, String password_)
 {
  switch (algorithm_)
  {
   //----- write how to verify new algorithm here -----//
   case 2:
   default:
   //----- change with your current verification algorithm -----//
    return this.hash_password(plaintext_, salt_, algorithm_).equalsIgnoreCase(password_);
  }
 }

 public String safe_random_salt()
 {
  String salt = Base64.getEncoder().encodeToString(hash("SHA-512", UUID.randomUUID().toString().replace("-", "")).getBytes());
  return salt.substring(0, salt.length() > 254 ? 254 : salt.length() - 1);
 }

 public boolean password_check(int iduser_, String plaintext_)
 {
  String[] row = this.db_get_password(iduser_);
  int algorithm_ = Integer.parseInt(row[ALGORITHM_COLUMN]);
  if ( this.verify_password(plaintext_, row[SALT_COLUMN], algorithm_, row[PASSWORD_COLUMN]) )
  {
   if ( algorithm_ < this.latestalgorithm_ )
   {
    salt_ = this.safe_random_salt();
    this.db_update_password(iduser_, plaintext_, salt_);
   }
   return true;
  }
  else
  {
   return false;
  }
 }

 public void bcrypt_test()
 {
  //https://www.php.net/manual/en/function.password-hash.php
  double timeTarget = 50; // 50 milliseconds 
  int cost = 8;
  long start, end;
  do 
  {
   cost++;
   start = System.currentTimeMillis();
   BCrypt.with(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.none()).hashToString(cost, "test".toCharArray());
   end = System.currentTimeMillis();
  } 
  while ((end - start) < timeTarget);

  System.out.println("Appropriate Cost Found: " + cost);
 }

 @SuppressWarnings("UseSpecificCatch")
 public void db_connect(String servername, String username, String password, String dbname)
 {
    String url    = "jdbc:mysql://" + servername + "/" + dbname + "?sessionVariables=sql_mode=\"STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION\"";
    try 
    {
        this.conn_ = DriverManager.getConnection(url, username, password);
    } 
    catch (Exception ex) 
    {
        Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
    }
 }
 
 @SuppressWarnings("UseSpecificCatch")
 public void db_close()
 {
  try 
  {
   this.conn_.close();
  } 
  catch (Exception ex) 
  {
   Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
  }
 }

 @SuppressWarnings("UseSpecificCatch")
 public String[] db_get_password(int iduser_)
 {   
  String[] row = new String[3];
  try 
  {
   if (!this.conn_.isClosed())
   {   
      PreparedStatement stmt = this.conn_.prepareStatement("select " + tuser.saltFieldname + ", " + tuser.passwordFieldname + ", " + tuser.algorithmFieldname + " from " + tuser.userTablename + " where " + tuser.iduserFieldname + "=? limit 0,1");
      stmt.setInt(1, iduser_);
      ResultSet result = stmt.executeQuery();
      while (result.next())
      {
        row[SALT_COLUMN]         = result.getString("salt");
        row[PASSWORD_COLUMN]     = result.getString("password");
        row[ALGORITHM_COLUMN]    = "" + result.getInt("algorithm");
        stmt.close();
        return row;
      }  
    }
   } 
   catch (Exception ex) 
   {
    Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
   }
   return row;
 }

 @SuppressWarnings("UseSpecificCatch")
 public void db_update_password(int iduser_, String plaintext_, String salt_)
 {
  try 
  {
   if (!this.conn_.isClosed())
   {   
    PreparedStatement stmt = this.conn_.prepareStatement("update " + tuser.userTablename + " set " + tuser.saltFieldname + "=?, " + tuser.algorithmFieldname + "=?, " + tuser.passwordFieldname + "=? where " + tuser.iduserFieldname + "=?");
    String newpass = this.hash_password(plaintext_, salt_, this.latestalgorithm_);
    stmt.setString(1, salt_);
    stmt.setInt(2, this.latestalgorithm_);
    stmt.setString(3, newpass);
    stmt.setInt(4, iduser_);
    stmt.execute();
    stmt.close();
   }
  } 
  catch (Exception ex) 
  {
   Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
  }
 }

 @SuppressWarnings("UseSpecificCatch")
 public void db_simulate_insert(int[] ids_)
 {
  try 
  {
   if (!this.conn_.isClosed())
   { 
    @SuppressWarnings({"UnusedAssignment", "LocalVariableHidesMemberVariable"})
    String salt_ = "";
    for (int i=0; i < ids_.length; i++)
    {
     salt_ = this.safe_random_salt();
     PreparedStatement stmt = this.conn_.prepareStatement("insert into " + tuser.userTablename + " (" + tuser.iduserFieldname + ", " + tuser.passwordFieldname + ", " + tuser.saltFieldname + ", " + tuser.algorithmFieldname + ") values (?, ?, ?, ?) on duplicate key update " + tuser.iduserFieldname + "=" + tuser.iduserFieldname + "");
     String pass  = this.hash_password("this is a password", salt_, this.latestalgorithm_);
     stmt.setInt(1, ids_[i]);
     stmt.setString(2, pass);
     stmt.setString(3, salt_);
     stmt.setInt(4, this.latestalgorithm_);
     stmt.execute();
     stmt.close();
    }
   }
  } 
  catch (Exception ex) 
  {
   Logger.getLogger(pass_sec.class.getName()).log(Level.SEVERE, null, ex);
  }
 }

 public void set_tuser(String saltFieldname, String passwordFieldname, String algorithmFieldname, String userTablename, String iduserFieldname) 
 {
  this.tuser.set_table_user(saltFieldname, passwordFieldname, algorithmFieldname, userTablename, iduserFieldname);
 }
}

|-Testing Persiapan

Contoh input (PHP):

<?php
$pass_test = new pass_sec();
$pass_test->db_connect('localhost', 'yourdbuser', 'yourdbpass', 'dbname');
$pass_test->db_simulate_insert([1,2]);
echo '<br/>Test ID 1 with right password '.$pass_test->password_check(1, 'this is a password');
echo '<br/>Test ID 2 with right password '.$pass_test->password_check(2, 'this is a password');
echo '<br/>Test ID 1 with wrong password '.$pass_test->password_check(1, 'this is a passwords');
echo '<br/>Test ID 2 with wrong password '.$pass_test->password_check(2, 'this is a passwords');

Contoh input (Java):

pass_sec pass_test = new pass_sec();
pass_test.db_connect("localhost", "yourdbuser", "yourdbpass", "dbname");
int[] newId = {1, 2};
pass_test.db_simulate_insert(newId);
System.out.println("Test ID 1 with right password " + pass_test.password_check(1, "this is a password"));
System.out.println("Test ID 2 with right password " + pass_test.password_check(2, "this is a password"));
System.out.println("Test ID 1 with wrong password " + pass_test.password_check(1, "this is a passwords"));
System.out.println("Test ID 2 with wrong password " + pass_test.password_check(2, "this is a passwords"));

Contoh di tabel:

password yang tersimpan di basis data
password yang tersimpan di basis data

|-Perubahan Hash

|-Perubahan Hash (PHP)

Apabila ada perubahan hash, maka cukup lakukan modifikasi di 3 tempat, yaitu:

  1. //—– +1 if new algorithm added —–//
    inkremen $this->latestalgorithm_, misalnya menjadi $this->latestalgorithm_ = 2;
  2. //—– write new algorithm here —–//
    ditambahkan algoritma, misalnya menjadi
//----- write new algorithm here -----//
case 2:
    $options = ['cost' => 12];
    $hashedplaintext = base64_encode(hash('sha256', $plaintext_ . $salt_ . $this->pepper_));
    return password_hash($hashedplaintext, PASSWORD_BCRYPT, $options);
  1. //—– write how to verify new algorithm here —–//
    ditambahkan algoritma, misalnya menjadi
//----- write how to verify new algorithm here -----//
   case 2:
    $hashedplaintext = base64_encode(hash('sha256', $plaintext_ . $salt_ . $this->pepper_));
    return password_verify($hashedplaintext, $password_);

Sehingga skripnya menjadi seperti di bawah ini:

 function pass_sec()
 {
  //----- must be change once -----//
  $this->pepper_          = 'MGVhYTI3MDEwZW=';
  //----- +1 if new algorithm added -----//
  $this->latestalgorithm_ = 2;
 }
 
 function hash_password($plaintext_, $salt_, $algorithm_)
 {
  switch ($algorithm_)
  {
   //----- write new algorithm here -----//
   case 2:
    $options = ['cost' => 12];
    $hashedplaintext = base64_encode(hash('sha256', $plaintext_ . $salt_ . $this->pepper_));
    return password_hash($hashedplaintext, PASSWORD_BCRYPT, $options);
   default:
   //----- change with your current algorithm -----//
   //----- maybe return plaintext_; -----//
   //----- or return hash('md5', $plaintext_); -----//
    return hash('sha512', $plaintext_ . $salt_ . $this->pepper_);
  }
 }
 
 function verify_password($plaintext_, $salt_, $algorithm_, $password_)
 {
  switch ($algorithm_)
  {
   //----- write how to verify new algorithm here -----//
   case 2:
    $hashedplaintext = base64_encode(hash('sha256', $plaintext_ . $salt_ . $this->pepper_));
    return password_verify($hashedplaintext, $password_);
   default:
   //----- change with your current verification algorithm -----//
    return $this->hash_password($plaintext_, $salt_, $algorithm_) == $password_;
  }
 }

|-Perubahan Hash (Java)

Apabila ada perubahan hash, maka cukup lakukan modifikasi di 3 tempat, yaitu:

  1. //—– +1 if new algorithm added —–//
    inkremen this.latestalgorithm_, misalnya menjadi $this.latestalgorithm_ = 2;
  2. //—– write new algorithm here —–//
    ditambahkan algoritma, misalnya menjadi
//----- write new algorithm here -----//
   case 2:
    int cost = 12;
    String hashedplaintext = Base64.getEncoder.encodeToString(hash("SHA-256", plaintext_ + salt_ + this.pepper_)).toString();
    return BCrypt.with(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.none()).hashToString(cost, hashedplaintext.toCharArray());
  1. //—– write how to verify new algorithm here —–//
    ditambahkan algoritma, misalnya menjadi
//----- write how to verify new algorithm here -----//
   case 2:
    String hashedplaintext = Base64.getEncoder.encodeToString(hash("SHA-256", plaintext_ + salt_ + this.pepper_)).toString();
    return BCrypt.verifyer(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.none()).verify(hashedplaintext.toCharArray(), password_.getBytes()).verified;

Sehingga skripnya menjadi seperti di bawah ini:

import at.favre.lib.crypto.bcrypt.BCrypt;
import at.favre.lib.crypto.bcrypt.LongPasswordStrategies;
 
public pass_sec()
 {
  //----- must be change once -----//
  this.pepper_          = "MGVhYTI3MDEwZW=";
  //----- +1 if new algorithm added -----//
  this.latestalgorithm_ = 2;
 }

 public String hash_password(String plaintext_, String salt_, int algorithm_)
 {
  switch (algorithm_)
  {
   //----- write new algorithm here -----//
   case 2:
    int cost = 12;
    String hashedplaintext = Base64.getEncoder.encodeToString(hash("SHA-256", plaintext_ + salt_ + this.pepper_)).toString();
    return BCrypt.with(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.none()).hashToString(cost, hashedplaintext.toCharArray());
   default:
   //----- change with your current algorithm -----//
   //----- maybe return plaintext_; -----//
   //----- or return hash("md5", plaintext_); -----//
    return hash("SHA-512", plaintext_ + salt_ + this.pepper_).toString();
  }
 }

 public boolean verify_password(String plaintext_, String salt_, int algorithm_, String password_)
 {
  switch (algorithm_)
  {
   //----- write how to verify new algorithm here -----//
   case 2:
    String hashedplaintext = Base64.getEncoder.encodeToString(hash("SHA-256", plaintext_ + salt_ + this.pepper_)).toString();
    return BCrypt.verifyer(BCrypt.Version.VERSION_2Y, LongPasswordStrategies.none()).verify(hashedplaintext.toCharArray(), password_.getBytes()).verified;
   default:
   //----- change with your current verification algorithm -----//
    return this.hash_password(plaintext_, salt_, algorithm_).equalsIgnoreCase(password_);
  }
 }
}

|-Testing Upgrade Algoritma Password

Contoh input (PHP):

<?php
$pass_test = new pass_sec();
$pass_test->db_connect('localhost', 'yourdbuser', 'yourdbpass', 'dbname');
$pass_test->db_simulate_insert([3,4]);
echo '<br/>Test ID 1 with right password '.$pass_test->password_check(1, 'this is a password');
echo '<br/>Test ID 2 with right password '.$pass_test->password_check(2, 'this is a password');
echo '<br/>Test ID 1 with wrong password '.$pass_test->password_check(1, 'this is a passwords');
echo '<br/>Test ID 2 with wrong password '.$pass_test->password_check(2, 'this is a passwords');
echo '<br/>Test ID 3 with right password '.$pass_test->password_check(3, 'this is a password');
echo '<br/>Test ID 4 with right password '.$pass_test->password_check(4, 'this is a password');
echo '<br/>Test ID 3 with wrong password '.$pass_test->password_check(3, 'this is a passwords');
echo '<br/>Test ID 4 with wrong password '.$pass_test->password_check(4, 'this is a passwords');

Contoh input (Java):


pass_sec pass_test = new pass_sec();
pass_test.db_connect("localhost", "yourdbuser", "yourdbpass", "dbname");
int[] newId = {1, 2};
pass_test.db_simulate_insert(newId);
System.out.println("Test ID 1 with right password " + pass_test.password_check(1, "this is a password"));
System.out.println("Test ID 2 with right password " + pass_test.password_check(2, "this is a password"));
System.out.println("Test ID 1 with wrong password " + pass_test.password_check(1, "this is a passwords"));
System.out.println("Test ID 2 with wrong password " + pass_test.password_check(2, "this is a passwords"));
System.out.println("Test ID 3 with right password " + pass_test.password_check(3, "this is a password"));
System.out.println("Test ID 4 with right password " + pass_test.password_check(4, "this is a password"));
System.out.println("Test ID 3 with wrong password " + pass_test.password_check(3, "this is a passwords"));
System.out.println("Test ID 4 with wrong password " + pass_test.password_check(4, "this is a passwords"));

Contoh di tabel:

password yang tersimpan di basis data algoritma 2
password yang tersimpan di basis data algoritma 2

By basit

Biro Pengembangan Teknologi Dan Sistem Informasi

One reply on “General Purpose Hashing vs Password Hashing”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.