MySQL新特性之mysql_config_editor 加密算法与解密实现

5880阅读 0评论2016-01-19 zxszcaijin
分类:Mysql/postgreSQL

   mysql_config_editor采用的AES ECB加密。关于AES 的ECB加密通常都是块加密,如果要加密超过块大小的数据,就需要涉及填充和链加密模式,文中提到的ECB就是指链加密模式。这篇文章主要介绍在该工具中该加密技术的使用与实现,并未详细介绍该机密技术的算法与实现细节。

   

 在前一篇文章中,加密的过程如下:
 encrypt_and_write_file->encrypt_buffer->my_aes_encrypt
 
 my_aes_encrypt的具体实现如下:

点击(此处)折叠或打开

  1. int my_aes_encrypt(const char* source, int source_length, char* dest,
  2. const char* key, int key_length)
  3. {
  4. #if defined(HAVE_YASSL)
  5. TaoCrypt::AES_ECB_Encryption enc;
  6. /* 128 bit block used for padding */
  7. uint8 block[MY_AES_BLOCK_SIZE];
  8. int num_blocks; /* number of complete blocks */
  9. int i;
  10. #elif defined(HAVE_OPENSSL)
  11. MyCipherCtx ctx;
  12. int u_len, f_len;
  13. #endif

  14. /* The real key to be used for encryption */
  15. uint8 rkey[AES_KEY_LENGTH / 8];
  16. int rc; /* result codes */

  17. if ((rc= my_aes_create_key(key, key_length, rkey)))
  18. return rc;

  19. #if defined(HAVE_YASSL)
  20. enc.SetKey((const TaoCrypt::byte *) rkey, MY_AES_BLOCK_SIZE);

  21. num_blocks = source_length / MY_AES_BLOCK_SIZE;

  22. for (i = num_blocks; i > 0; i--) /* Encode complete blocks */
  23. {
  24. enc.Process((TaoCrypt::byte *) dest, (const TaoCrypt::byte *) source,
  25. MY_AES_BLOCK_SIZE);
  26. source += MY_AES_BLOCK_SIZE;
  27. dest += MY_AES_BLOCK_SIZE;
  28. }

  29. /* Encode the rest. We always have incomplete block */
  30. char pad_len = MY_AES_BLOCK_SIZE - (source_length -
  31. MY_AES_BLOCK_SIZE * num_blocks);
  32. memcpy(block, source, 16 - pad_len);
  33. memset(block + MY_AES_BLOCK_SIZE - pad_len, pad_len, pad_len);

  34. enc.Process((TaoCrypt::byte *) dest, (const TaoCrypt::byte *) block,
  35. MY_AES_BLOCK_SIZE);

  36. return MY_AES_BLOCK_SIZE * (num_blocks + 1);
  37. #elif defined(HAVE_OPENSSL)
  38. if (! EVP_EncryptInit(&ctx.ctx, EVP_aes_128_ecb(),
  39. (const unsigned char *) rkey, NULL))
  40. return AES_BAD_DATA; /* Error */
  41. if (! EVP_EncryptUpdate(&ctx.ctx, (unsigned char *) dest, &u_len,
  42. (unsigned const char *) source, source_length))
  43. return AES_BAD_DATA; /* Error */
  44. if (! EVP_EncryptFinal(&ctx.ctx, (unsigned char *) dest + u_len, &f_len))
  45. return AES_BAD_DATA; /* Error */

  46. return u_len + f_len;
  47. #endif
  48. }

上述程序就是mysql的使用AES的机密过程。在加密中,如果mysql定义了自带的AES加密算法,就使用自带的(#define HAVE_YASSL).否则就是用OPENSSL EVP框架的加密算法。

这里介绍OPENSSL EVP加密算法的步骤:

点击(此处)折叠或打开

  1. int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv)
  2. EVP_EncryptInit (初始化)
  3.                |
  4.                |
  5.                V
  6.  EVP_EncryptUpdate(&ctx,out+len,&outl,in,inl);
  7.  EVP_EncryptUpdate(这个EVP_EncryptUpdate的实现实际就是将明文按照16 bytes的长度去加密,实现会取得该cipher的块大小(对aes_128来说是16字节)并将block-size的整数倍去加密。如果输入为50字节,则此处仅加密48字节,outl也为48字节。输入in中的最后两字节拷贝到ctx->buf缓存起来。
  8. 对于inl为block_size整数倍的情形,且ctx->buf并没有以前遗留的数据时则直接加解密操作,省去很多后续工作)
  9.                |
  10.                |
  11.                V
  12.    EVP_EncryptFinal_ex(&ctx,out+len,&outl);
  13.    对于如本例所述,第一次除了了48字节余两字节,第二次处理了第一次余下的2字节及46字节,余下了输入100字节中的最后4字节。此处进行处理。如果不支持pading,且还有数据的话就出错,否则,将block_size-待处理字节数个数个字节设置为此个数的值,如block_size=16,数据长度为4,则将后面的12字节设置为16-4=12,补齐为一个分组后加密。对于前面为整分组时,如输入数据为16字节,最后再调用此Final时,不过是对16个0进行加密,此密文不用即可,也根本用不着调一下这Final。
由于我们知道了,在加密后的文件中,KEY是存放在文件头部 offset 4bytes的地方,之后的20bytes 存放的都是key的信息 。所以我们只要读取该key,然后对该key之后的信息一行一行的用该key 调用解密程序就好了。具体的实现如下:
 
algo_aes_ecb.h

点击(此处)折叠或打开

  1. #ifndef ALGO_AES_H
  2. #define ALGO_AES_H

  3. int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, unsigned char *ciphertext);

  4. int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *plaintext);

  5. #endif

algo_aes_ecb.c

点击(此处)折叠或打开

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include "algo_aes_ecb.h"
  5. #include <openssl/evp.h>
  6. #include <openssl/aes.h>

  7. typedef unsigned char uint8;
  8. #define AES_KEY_LENGTH 128

  9. uint8 rkey[AES_KEY_LENGTH / 8];

  10. void handleErrors(void)
  11. {
  12. ERR_print_errors_fp(stderr);
  13. abort();
  14. }


  15. static int my_aes_create_key(const char *key, int key_length, uint8 *rkey)
  16. {
  17. uint8 *rkey_end= rkey + AES_KEY_LENGTH / 8;
  18. uint8 *ptr;
  19. const char *sptr;
  20. const char *key_end= key + key_length;

  21. memset(rkey, 0, AES_KEY_LENGTH / 8);

  22. for (ptr= rkey, sptr= key; sptr < key_end; ptr ++, sptr ++)
  23. {
  24. if (ptr == rkey_end)
  25. ptr= rkey;
  26. *ptr ^= (uint8) *sptr;
  27. }
  28. }

  29. int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key,unsigned char *ciphertext)
  30. {
  31. EVP_CIPHER_CTX *ctx;

  32. int len;

  33. int ciphertext_len;
  34. my_aes_create_key(key,20,rkey);
  35. /* Create and initialise the context */
  36. if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  37. /* Initialise the encryption operation. IMPORTANT - ensure you use a key
  38. * and IV size appropriate for your cipher
  39. * In this example we are using 128 bit AES (i.e. a 128 bit key).
  40. */

  41. if(1 != EVP_EncryptInit(ctx, EVP_aes_128_ecb(),(const unsigned char *)rkey,NULL))
  42. handleErrors();

  43. /* Provide the message to be encrypted, and obtain the encrypted output.
  44. * EVP_EncryptUpdate can be called multiple times if necessary
  45. *
  46. */
  47. if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, (unsigned const char *)plaintext, plaintext_len))
  48. handleErrors();
  49. ciphertext_len = len;

  50. /* Finalise the encryption. Further ciphertext bytes may be written at
  51. * * * this stage.
  52. * * */
  53. if(1 != EVP_EncryptFinal(ctx, ciphertext + len, &len)) handleErrors();
  54. ciphertext_len += len;

  55. /* Clean up */
  56. EVP_CIPHER_CTX_free(ctx);

  57. return ciphertext_len;
  58. }

  59. int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *plaintext)
  60. {
  61. EVP_CIPHER_CTX *ctx;

  62. int len;

  63. int plaintext_len;
  64. my_aes_create_key(key,20,rkey);

  65. /* Create and initialise the context */
  66. if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  67. /* Initialise the decryption operation. IMPORTANT - ensure you use a key
  68. * size appropriate for your cipher
  69. * In this example we are using 128 bit AES (i.e. a 128 bit key). The
  70. */

  71. if(1 != EVP_DecryptInit(ctx, EVP_aes_128_ecb(),rkey,NULL))
  72. handleErrors();

  73. /* Provide the message to be decrypted, and obtain the plaintext output.
  74. * EVP_DecryptUpdate can be called multiple times if necessary
  75. *
  76. */
  77. if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
  78. handleErrors();
  79. plaintext_len = len;

  80. /* Finalise the decryption. Further plaintext bytes may be written at
  81. * * * this stage.
  82. * * */
  83. if(1 != EVP_DecryptFinal(ctx, plaintext + len, &len)) handleErrors();
  84. plaintext_len += len;

  85. /* Clean up */
  86. EVP_CIPHER_CTX_free(ctx);

  87. return plaintext_len;
  88. }

dynstring.h

点击(此处)折叠或打开

  1. #ifndef dynstring_h
  2. #define dynstring_h

  3. #include<stdlib.h>
  4. typedef struct st_dynamic_string
  5. {
  6. char *str;
  7. size_t length,max_length,alloc_increment;
  8. } DYNAMIC_STRING;

  9. #endif

dynstring.c

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<string.h>
  4. #include <stdbool.h>
  5. #include "dynstring.h"
  6. #define NullS (char *) 0


  7. bool init_dynamic_string(DYNAMIC_STRING *str, const char *init_str,size_t init_alloc, size_t alloc_increment)
  8. {

  9. size_t length;

  10. if (!alloc_increment)
  11. alloc_increment=128;
  12. length=1;
  13. if (init_str && (length= strlen(init_str)+1) < init_alloc)
  14. init_alloc=((length+alloc_increment-1)/alloc_increment)*alloc_increment;
  15. if (!init_alloc)
  16. init_alloc=alloc_increment;

  17. if (!(str->str=(char*)malloc(init_alloc))) return false;
  18. str->length=length-1;
  19. if (init_str)
  20. memcpy(str->str,init_str,length);
  21. str->max_length=init_alloc;
  22. str->alloc_increment=alloc_increment;

  23. return true;
  24. }


  25. bool dynstr_append_mem(DYNAMIC_STRING *str, const char *append,size_t length)
  26. {
  27. char *new_ptr;
  28. if (str->length+length >= str->max_length)
  29. {
  30. size_t new_length=(str->length+length+str->alloc_increment)/
  31. str->alloc_increment;
  32. new_length*=str->alloc_increment;

  33. if (!(new_ptr=(char*) realloc(str->str,new_length)))
  34. return true;
  35. str->str=new_ptr;
  36. str->max_length=new_length;
  37. }
  38. memcpy(str->str + str->length,append,length);
  39. str->length+=length;
  40. str->str[str->length]=0;
  41. return false;
  42. }

  43. bool dynstr_append(DYNAMIC_STRING *str, const char *append)
  44. {
  45. return dynstr_append_mem(str,append,(uint) strlen(append));
  46. }

  47. bool dynstr_trunc(DYNAMIC_STRING *str, size_t n)
  48. {
  49. str->length-=n;
  50. str->str[str->length]= '\0';
  51. return false;
  52. }


  53. char *strcend(register const char *s, register char c)
  54. {
  55. for (;;)
  56. {
  57. if (*s == (char) c) return (char*) s;
  58. if (!*s++) return (char*) s-1;
  59. }
  60. }


  61. bool dynstr_realloc(DYNAMIC_STRING *str, size_t additional_size)
  62. {

  63. if (!additional_size) return false;
  64. if (str->length + additional_size > str->max_length)
  65. {
  66. str->max_length=((str->length + additional_size+str->alloc_increment-1)/
  67. str->alloc_increment)*str->alloc_increment;
  68. if (!(str->str=(char*) realloc(str->str,str->max_length)))
  69. return true;
  70. }
  71. return false;
  72. }


  73. void dynstr_free(DYNAMIC_STRING *str)
  74. {
  75. free(str->str);
  76. str->str= NULL;
  77. }

decrypt.c

点击(此处)折叠或打开

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include <sys/types.h>
  5. #include<unistd.h>
  6. #include<fcntl.h>
  7. #include "dynstring.h"
  8. #include "algo_aes_ecb.h"


  9. #define LOGIN_KEY_LEN 20U
  10. #define MY_LINE_MAX 4096
  11. #define MAX_CIPHER_STORE_LEN 4U
  12. #define FN_REFLEN 256

  13. #define O_BINARY 0

  14. typedef unsigned char uchar;
  15. #define MY_LOGIN_HEADER_LEN (4 + LOGIN_KEY_LEN)


  16. #define int4store(T,A) do { *((char *)(T))=(char) ((A));\
  17. *(((char *)(T))+1)=(char) (((A) >> 8));\
  18. *(((char *)(T))+2)=(char) (((A) >> 16));\
  19. *(((char *)(T))+3)=(char) (((A) >> 24));\
  20. } while(0)

  21. #define sint4korr(A) (int) (((int) ((uchar) (A)[0])) +\
  22. (((int) ((uchar) (A)[1]) << 8)) +\
  23. (((int) ((uchar) (A)[2]) << 16)) +\
  24. (((int) ((uchar) (A)[3]) << 24)))

  25. static char my_key[LOGIN_KEY_LEN];
  26. static char my_login_file[FN_REFLEN];
  27. static int g_fd;
  28. const int access_flag= (O_RDWR | O_BINARY);

  29. static int read_login_key(void)
  30. {

  31. /* Move past the unused buffer. */
  32. if (lseek(g_fd, 4, SEEK_SET) != 4)
  33. exit(1); /* Error while lseeking. */

  34. if (read(g_fd, (uchar *)my_key, LOGIN_KEY_LEN)!= LOGIN_KEY_LEN)
  35. exit(1);

  36. }

  37. static int read_and_decrypt_file(DYNAMIC_STRING *file_buf)
  38. {

  39. char cipher[MY_LINE_MAX], plain[MY_LINE_MAX];
  40. uchar len_buf[MAX_CIPHER_STORE_LEN];
  41. int cipher_len= 0, dec_len= 0;

  42. /* Move past key first. */
  43. if (lseek(g_fd, MY_LOGIN_HEADER_LEN, SEEK_SET )
  44. != (MY_LOGIN_HEADER_LEN))
  45. goto error; /* Error while lseeking. */

  46. /* First read the length of the cipher. */
  47. while (read(g_fd, len_buf, MAX_CIPHER_STORE_LEN) == MAX_CIPHER_STORE_LEN)
  48. {
  49. cipher_len= sint4korr(len_buf);

  50. if (cipher_len > MY_LINE_MAX)
  51. goto error;

  52. /* Now read 'cipher_len' bytes from the file. */
  53. if ((int) read(g_fd, (uchar *) cipher, cipher_len) == cipher_len)
  54. {
  55. if ((dec_len= decrypt(cipher, cipher_len, my_key,plain)) < 0)
  56. goto error;

  57. plain[dec_len]= 0;
  58. dynstr_append(file_buf, plain);
  59. }
  60. }

  61. return 0;

  62. error:
  63. printf("couldn't decrypt the file");
  64. return -1;
  65. }


  66. int my_default_get_login_file(char *file_name, size_t file_name_size)
  67. {
  68. size_t rc;

  69. if (getenv("MYSQL_TEST_LOGIN_FILE"))
  70. rc= snprintf(file_name, file_name_size, "%s",
  71. getenv("MYSQL_TEST_LOGIN_FILE"));

  72. else if (getenv("HOME"))
  73. rc= snprintf(file_name, file_name_size, "%s/.mylogin.cnf",
  74. getenv("HOME"));
  75. else
  76. {
  77. memset(file_name, 0, file_name_size);
  78. return 0;
  79. }
  80. /* Anything <= 0 will be treated as error. */
  81. if (rc <= 0)
  82. return 0;

  83. return 1;
  84. }

  85. int main(int argc,char **argv){

  86. DYNAMIC_STRING file_buf;
  87. if(!my_default_get_login_file(my_login_file,sizeof(my_login_file))){
  88. printf("logfile file not found \n");
  89. goto error;
  90. }

  91. if((g_fd= open(my_login_file, access_flag)) == -1)
  92. {
  93. printf("couldn't open the file\n");
  94. goto error;
  95. }
  96. init_dynamic_string(&file_buf, "",256, MY_LINE_MAX);

  97. read_login_key();

  98. read_and_decrypt_file(&file_buf);
  99. printf("%s",file_buf.str);

  100. error:
  101. dynstr_free(&file_buf);
  102. }

makefile文件

点击(此处)折叠或打开

  1. OBJ_DIR = ./obj
  2. BIN_DIR = ./bin
  3. SRC_DIR = ./
  4. OBJS = \
  5. $(OBJ_DIR)/algo_aes_ecb.o \
  6. $(OBJ_DIR)/dynstring.o \
  7. $(OBJ_DIR)/decrypt.o
  8. TARGET = decrypt
  9. INC_OPT = -I./
  10. LNK_OPT = -lssl

  11. $(BIN_DIR)/$(TARGET) : clean chkobjdir chkbindir $(OBJS)
  12. gcc -g -o $@ $(OBJS) $(LNK_OPT)

  13. $(OBJ_DIR)/algo_aes_ecb.o : algo_aes_ecb.c
  14. gcc -g $(INC_OPT) -c -o $@ $<

  15. $(OBJ_DIR)/decrypt.o : decrypt.c
  16. gcc -g $(INC_OPT) -c -o $@ $<

  17. $(OBJ_DIR)/dynstring.o : dynstring.c
  18. gcc -g $(INC_OPT) -c -o $@ $<

  19. chkobjdir :
  20. @if test ! -d $(OBJ_DIR) ; \
  21. then \
  22. mkdir $(OBJ_DIR) ; \
  23. fi

  24. chkbindir :
  25. @if test ! -d $(BIN_DIR) ; \
  26. then \
  27. mkdir $(BIN_DIR) ; \
  28. fi

  29. clean :
  30. rm -rf $(TARGET)
  31. rm -rf $(OBJS)

执行该二进制文件,就可以得到加密之后的密码了:

 
该程序只是解密了默认情况下的加密文件,也就是$HOME/.mylogin.cnf 文件中存放的加密信息。如果文件存放在其他地方,则可以修改代码,指定文件所在位置。
总结:
从整个分析可以看到,mysql_config_editor使用了AES ECB 128bit加密算法,加密简单。而且,加密的秘钥也存放在加密文件开头部分。因此,只要该文件可读,就很容易通过该key来解密。但相比之前的版本,该特性起码能够防止命令行输入密码,这样很容易泄露密码,特别是泄露非localhost的密码,使得不怀好意的黑客通过远程访问。该程序的加密,至少能够防止绝大多数人知道明文密码或者防止那些不了解加密策略的人知道密码,从而降低了安全隐患。

上一篇:MySQL新特性之mysql_config_editor源码解析
下一篇:redis lru实现策略