LoginSignup
4
1

More than 3 years have passed since last update.

【C言語】Linuxでのcrypt関数の使用法【パスワードハッシュ化】

Last updated at Posted at 2020-01-16

0.はじめに

使う機会があり調べたことをまとめる。
Linuxの機能を用いているらしいため、他の言語で使ったことがある人は多分同じ感覚で使えると思う。

筆者のテスト環境
Ubuntu 18.04.3 LTS
コンパイラ gcc 7.4.0

crypt()関数とcrypt_r()関数の二つがあるが、crypt_r()関数はcrypt()関数のリエントラント版、
つまりcrypt()関数はマルチスレッド安全でない一方、crypt_r()関数はマルチスレッド安全である。
2つの関数についてそれぞれ使用例を挙げた後、解説をしようと思う。

注:crypt関数の仕様はOSによって異なるので、正確な内容を知りたい場合はman 3 cryptコマンドを使ってマニュアルを読んでください。
以下の内容はGNU/Linux(Ubuntu18.0.4)での話です。

1.crypt()関数

プロトタイプ宣言は次の通り。
char *crypt(const char *key, const char *salt);
key はハッシュ化したい文字列、
saltはハッシュ化の際に使用する文字列。

戻り値は、
idを指定しなかった場合はハッシュ化されたkey,
idを指定した場合、 (saltの文字列) + "$" + (ハッシュ化されたkey)。
(idについては後述。)

1.1 crypt()関数の使用例

crypt_test.c
#include<crypt.h>
#include<stdio.h>
#include<string.h>
#define BUFSIZE 1024

int main(void){
  char key[BUFSIZE] = "key"; // ハッシュ化したい文字列
  char salt_origin[BUFSIZE] = "example"; //ソルトの文字列
  char salt[BUFSIZE]; 
  char encrypted[BUFSIZE]; // 結果格納用

  sprintf(salt, "$6$%s", salt_origin); // ソルトの整形、id指定(後述)
  strcpy(encrypted, crypt(key, salt)); //encrypted変数には、
                                       //文字列"$6$example$(ハッシュ化されたkey)"が格納される

  printf("%s\n", encrypted);
}

コンパイルの際には-lcryptオプションをつけること。

2.crypt_r()関数

プロトタイプ宣言は次の通り。
char *crypt_r(const char *key, const char *salt, struct crypt_data *data);

crypt_data構造体はcrypt.hヘッダファイル内で定義されており、ハッシュ結果保存のために使用される。


crypt_data構造体の定義
struct crypt_data
{
char keysched[16 * 8];
char sb0[32768];
char sb1[32768];
char sb2[32768];
char sb3[32768];
char crypt_3_buf[14];
char current_salt[2];
long int current_saltbits;
int direction, initialized;
};

crypt_data構造体の変数は、1度目のcrypt_r()関数使用でその構造体変数を渡す前にinitializedメンバ変数を0にする必要がある。
keyschedメンバ変数にはハッシュ化した後のkeyが格納される。

2.1.crypt_r()関数の使用例

crypt_r_test.c
#define _GNU_SOURCE // crypt_r()関数を使用するためには、このマクロ定義を
                    //どのファイルのインクルードよりも前に記述する必要がある
#include<crypt.h>
#include<stdio.h>

#define BUFSIZE 1024

int main(void){
  char key[BUFSIZE] = "key"; // ハッシュ化したい文字列
  char salt_origin[BUFSIZE] = "example"; // ソルト。
  char salt[BUFSIZE];

  struct crypt_data data; // ハッシュ結果保存のために必要な構造体
  data.initialized = 0; // crypt_r()関数使用の前に行う必要有り。

  sprintf(salt, "$5$%s", salt_origin); // id指定(後述)

  crypt_r(key, salt, &data);
  printf("%s\n", data.keysched); //keyschedメンバ変数には、
                                 //文字列"$5$example$(ハッシュ化されたkey)"が格納される
}

コンパイルの際には-lcryptオプションをつけること。

2.2._GNU_SOURCEについて

crypt_r()関数を使うには、マクロ定義#define _GNU_SOURCEどのヘッダファイルのインクルードよりも前に行う必要がある。
ソースコードの最初に記述しておくのが無難である。
自分で定義したヘッダファイルを使用する際には、インクルードガードの直後に記述するのが無難である。


もしくは、
マクロ定義はコマンドラインからも行えるのでそちらで行ってもよい。
-D<マクロ名>[=値] というオプションをコンパイル実行時に渡してやればよい。
 例えば、コンパイル時に-DN=2というオプションを渡すのは、#define N 2をソースコード内で記述するのと同じ意味になる。
 今回の場合で言えば、#define _GNU_SOURCEをソースコードの最初に記述する代わりに、コンパイル時に-D_GNU_SOURCEというオプションを付け加えてもよい。
Makefileを使うならばこちらでもよいのではないだろうか。

3.saltについて

仕様可能文字は[a-zA-Z0-9./]。小文字のアルファベット、大文字のアルファベット、数字、'.'と'/'である。

crypt関数に渡すsaltのフォーマットを整えることによって、ハッシュアルゴリズムを指定することができる。
saltを$(id)$(saltの文字列)$という風にする。
idはハッシュアルゴリズムを指定するための番号である。(ハッシュアルゴリズムについては後述)
(ちなみにsaltの文字列の最後の$マークは省略可能である。)

例. salt = "$5$example";
この例では、ハッシュアルゴリズムをSHA-256で指定し、saltの文字列として"example"を指定している。

idを指定せずに文字列だけ渡すとDES方式でハッシュ化される。

3.1.saltのidとハッシュアルゴリズム

  • idとハッシュアルゴリズムの対応表
id ハッシュアルゴリズム
1 MD5
2a Blowfish(OSによっては使用できない可能性有)
5 SHA-256
6 SHA-512

無指定だとDESになる。DESはSaltを2文字しか使用せず、パスワードは8文字までしか認識しないため、とても脆弱であり、使用は推奨されない。

  • ハッシュ化後の文字列の文字数
ハッシュアルゴリズム ハッシュ化後の文字列の文字数
MD5 22文字
SHA-256 43文字
SHA-512 86文字
  • saltの文字数
    DES:2文字固定 (saltの文字列の先頭二文字のみ見る、それ以降の文字は無視される )
    MD5:8文字以内 (saltの文字列の先頭8文字のある分だけ見る、それ以降の文字は無視される)

ハッシュアルゴリズムの安全性について、
DES<MD5<SHA-256<SHA-512
と、idの数字が大きくなるほど安全性は高くなる。

4.終わりに

コンパイル時には-lcryptオプションを忘れないこと。

何か間違いなどございましたらご指摘ください。

5.参考

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/crypt.3.html
https://blog.amedama.jp/entry/unix-crypt-3

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1