FUSE Virtual Filesystem
[於HackMD中閱讀](https://hackmd.io/@kjmse716/B1B8T9CX1e)
# 如何使用
```bash
sudo apt update
gcc fuse libfuse-dev pkg-config make cmake libssl-dev uuid-dev
```
# FUSE (Filesystem in Userspace)
## 甚麼是 FUSE(Filesystem in Userspace)?
在開始 FUSE 實驗之前,我認為有必要先釐清一個核心問題:我們為什麼要選擇在「使用者空間」而不是傳統的「核心空間」來實作一個檔案系統?
* 傳統上,檔案系統是作業系統核心的一部分,直接在核心層開發雖然能帶來最高的執行效能,但這條路充滿了挑戰。開發者不僅需要面對複雜的同步問題和核心崩潰的風險,還得在一個受限的環境中(通常只能用 C 語言、無法使用標準函式庫)進行開發與除錯,整個過程極為困難且耗時。
FUSE (Filesystem in Userspace) 框架的出現,正是為了解決這些痛點。它讓我們可以像開發一般應用程式一樣,在熟悉的環境中使用豐富的函式庫、方便的除錯工具來建構檔案系統。雖然這犧牲了部分效能(因為操作需要在核心與使用者空間之間切換),但換來了無與倫比的開發彈性與速度。因此,本次實驗選擇 FUSE,主要考量便是優先享受其快速開發與高度彈性的優點,而非追求極致的 I/O 效能。
ClamFS (a FUSE-based user-space file system) VS Filesystem in kernel

* 上圖展示了 ClamFS 的檔案存取運作流程與傳統核心中 filesystem 的差異。
[ClamFS Github](https://github.com/burghardt/clamfs)
## **FUSE 核心運作流程**
* What is inside FUSE

上圖展示了`open()`檔案存取請求,是如何從使用者空間的應用程式,一路傳遞到我們撰寫的 FUSE filesystem 程式。
1. 第一步:來自應用程式的請求
一個普通的使用者空間應用程式需要存取掛載在 `/mnt/foo/` 下的檔案時,它會像存取任何其他檔案一樣,發起一個標準的系統呼叫,例如 `open("/mnt/foo/bar")`。在這個階段,應用程式本身並不知道、也不需要知道它正在跟一個 FUSE 檔案系統互動。
2. 第二步:核心 VFS 的分派
這個請求透過系統呼叫進入了核心空間 (Kernel Space),首先由**虛擬檔案系統 (VFS)** 層接收。VFS 是 Linux 檔案系統的抽象層,它扮演著「總機」的角色。當 VFS 解析路徑 `/mnt/foo/bar` 後,它會發現這個掛載點 (`/mnt/foo`) 的類型是 FUSE,於是它不會將請求交給硬碟的驅動程式 (如 EXT4),而是轉交給核心內的 **FUSE Driver**。
3. 第三步 & 第四步:Filesystem in userspace 的關鍵
由於核心中的 FUSE Driver 本身並不包含任何檔案系統的邏輯(例如如何建立檔案、如何列出目錄),它只是一個「橋樑」,因此會需要將使用者檔案存取的請求轉發給 User space 中我們寫的 FUSE 程式。
* **第三步** FUSE Driver 接收到 VFS 的請求後(例如 `lookup("foo")`),將這個操作打包成一個請求訊息,放入一個佇列 (QUEUE) 中,然後執行一次**上下文切換 (Context Switch)**,從核心空間跳回使用者空間。
* **第四步** 這個切換會喚醒我們一直在背景等待的 **FUSE Daemon** (也就是我們自己寫的 FUSE 程式)。我們的程式透過 **LIB FUSE** 函式庫,從 FUSE Driver 中讀取到這個請求訊息。
4. **第五步 & 第六步:執行自訂邏輯**
我們的 FUSE Daemon 被喚醒並收到請求後,就**開始執行我們自己寫的檔案系統邏輯**。
圖中的 `lookup()`、`getattr()`、`read()`、`write()` 等,就是我們在程式中實作的那些函式 (callbacks)。FUSE Daemon 會根據收到的請求類型,去呼叫對應的函式。
* 圖中的 **第五步和第六步** :如果我們的 FUSE Daemon 在設計上有需要去存取放在其他檔案系統中的資料,FUSE 框架支援去存取底層的其他檔案系統 (Lower FS) 例如 EXT4。這種檔案系統被稱為「堆疊式檔案系統 (Stackable File System)」。
ex: 我們可以實作一個加密檔案系統,當 `read()` 請求進來時,我們的 FUSE 程式就去底層的 EXT4 硬碟上讀取真正的加密檔案,解密後再把內容回傳。
5. **返回路徑**
在我們的 FUSE Daemon 處理完請求後(例如找到了檔案、讀取了內容),會將結果傳遞給 **LIB FUSE** ,並透過 write() system call 將資料寫回給核心的 FUSE Driver(透過 copy_from_user())。
最後,FUSE Driver 將結果循原路返回給 VFS,VFS 再將結果交給最初發起請求的應用程式。
FUSE 透過兩次「核心 <-> 使用者空間」的上下文切換,將傳統上屬於核心的複雜檔案系統邏輯,「外包」給了一個在使用者空間執行的、更安全、更易於開發和除錯的普通行程。
## FUSE 的核心組件

FUSE 系統本身的核心組件只有兩個:
* Kernel space
fuse.ko:唯一的 FUSE 核心模組,掛載在 VFS 下,並建立 /dev/fuse 這個字元裝置作為與使用者空間溝通的管道。
* User-space
libfuse.so:FUSE 使用者空間函式庫,提供 API 並負責與 /dev/fuse 溝通。
其中,fuse.ko 在實作方法上更像是一個特殊的字元裝置驅動:它的任務是把 VFS 的檔案操作請求搬到使用者空間,並把結果搬回來,同時也向 VFS 註冊 "fuse" 檔案系統型別,讓系統能識別並掛載 FUSE 檔案系統。
# 實作目標:加密檔案系統
## 什麼是加密檔案系統
在現代資訊環境中,資料不只在傳輸過程中需要保護,儲存在硬碟、SSD、雲端儲存等媒介上的靜態資料(data-at-rest)同樣面臨風險。這些資料可能因為設備遺失、被竊取、硬體報廢或遭到未授權存取而外洩。一旦攻擊者直接取得儲存裝置,如果沒有任何防護措施,檔案內容往往能被輕易讀取。而加密檔案系統(Encrypted File System, EFS)便是為此而生的核心技術之一。
在加密檔案系統中,檔案的內容在寫入儲存媒介之前,會先經過加密處理;而在讀取時,則會即時解密還原。這種設計的目的,是確保即使儲存裝置遺失、被竊取或遭到未授權存取,檔案內容依然無法被直接讀取。通常在加密檔案系統中,**每個檔案都有自己獨立的金鑰**。
* 這把金鑰在密碼學上通常稱為檔案加密金鑰 (File Encryption Key, FEK)。
* 每個檔案會生成一個獨立的、高強度的隨機金鑰(例如 AES-256)來加密該檔案的內容。
優點:隔離風險。即使攻擊者設法獲取了其中一個檔案的 FEK,其他所有檔案依然是安全的。
:::info
每個檔案的獨立加密金鑰存放於哪裡?
---
系統需要一個地方來存放所有加密過的 FEK。在實際的系統中,不是一個「單獨的檔案」去儲存所有的金鑰。常見的做法是,將加密後的 FEK 存放在對應檔案的元數據 (metadata) 或標頭 (header) 中。也就是說,加密後的金鑰跟著加密後的檔案內容一起存放。
這樣做的好處是管理方便,當使用者複製或移動檔案時,它的加密金鑰(的加密版本)也跟著一起移動了。
* 這個儲存金鑰的「檔案」需要透過一個使用者的主金鑰才能存取
這把主金鑰通常被稱為金鑰加密金鑰 (Key Encryption Key, KEK) 或主金鑰 (Master Key)。
系統會使用這把 KEK 去加密(或稱「封裝」)所有單獨的 FEK。因此,儲存在硬碟上的 FEK 都是密文狀態。
:::
## 當使用者需要讀取一個檔案時,系統的運作流程
1. 使用者提供密碼 (Password)。
2. 系統使用一個稱為金鑰派生函式 (KDF) 的演算法(例如 PBKDF2, scrypt, Argon2)從使用者的密碼中運算產生出主金鑰 (KEK)。這個步驟非常重要,可以抵抗暴力破解。
3. 系統讀取檔案標頭,取得加密過的 FEK。
4. 使用主金鑰 (KEK) 將 FEK 解密出來,得到明文的檔案金鑰。
5. 最後,使用明文的 FEK 來解密檔案的實際內容。
::: info
為什麼要用這樣兩層式(或多層式)金鑰架構?
---
* 方便更換密碼:當使用者想更換密碼時,系統不需要將硬碟上所有的資料全部重新加密。只需要用新密碼產生一把新的主金鑰 (KEK),然後用這把新的主金鑰去把所有被加密的 FEK 重新加密一遍即可。這個過程非常快,因為需要重新加密的只有幾百或幾千個檔案金鑰,而不是海量的檔案內容。
* 更高的安全性:使用者的密碼通常強度不足(例如 password123),不適合直接用於檔案加密。透過 KDF 函式,可以將一個相對較弱的密碼「拉伸」成一個密碼學上強度非常高的主金鑰,大大增加了安全性。
* 支援細粒度的存取控制與撤銷
如果要撤銷某個使用者對某檔案的存取權,只要刪掉該檔案 metadata 中用該使用者公鑰加密的 FEK 副本即可。
單一主金鑰模式下,撤銷權限幾乎等於要換掉整個主金鑰並重新加密所有檔案。
但是這樣不是只要有了主金鑰就可以打開所有的檔案(即使每個檔案的加密金鑰不同),這與只有一個主金鑰加密所有的檔案,沒有每個檔案專屬的加密金鑰有什麼不同?
* 在某些加密模式下,如果攻擊者能同時獲取某個加密塊的明文和密文(這被稱為 Known-Plaintext Attack),他們可能可以推斷出關於金鑰的資訊。如果所有檔案都用同一把金鑰,攻擊者可以分析的資料就非常多,破解金鑰的風險隨之增加。
* 更換密碼變得可行
* 在多使用者系統中,可以為同一個檔案的 FEK 建立多份加密副本(每份用不同使用者的公鑰加密),存放在檔案 metadata。
:::
# FUSE filesystem lab
## 實驗目標
這個實驗的目標是設計並實作一個基於 FUSE (Filesystem in Userspace) 的加密檔案系統,來探索 User space file system 框架在安全性與可擴充性上的應用潛力。透過此實驗,我希望能達成以下幾點:
1. 理解 FUSE 架構:熟悉其在使用者態與核心態之間的互動機制,並掌握檔案系統掛載與操作流程。
2. 實作透明加密/解密機制:在檔案讀寫過程中自動進行加密與解密,確保儲存於磁碟上的資料在未經授權的情況下不可讀。
3. 驗證效能與正確性:透過測試不同檔案大小與操作模式,觀察加密檔案系統在效能上的影響,並確保檔案內容在加解密過程中保持一致性。
4. 加強系統實驗能力:透過從零到一地實作一個功能相對完整的加密檔案系統,提升對作業系統、檔案系統與資訊安全交互領域的理解與實作經驗。
5. 觀察與理解像是一個應用程式的 open() 或 write() 呼叫,在檔案系統層面到底是如何被處理的。
## FUSE installation
Install FUSE and all the dependencies:
```bash
$ sudo apt-get update
$ sudo apt install pkg-config
$ sudo apt-get install gcc fuse libfuse-dev make cmake
```
To check the FUSE version:
```bash
$ fusermount-V
```
## Open SSL instalation
```bash
sudo apt install libssl-dev
```
## 實作
### add_file()
```c=39
void add_file( const char *filename )
{
printf("add_file start\n");
curr_file_idx++;
strcpy( files_list[ curr_file_idx ], filename );
files_content[curr_file_idx] = file_metadata_init();
printf("metadata_init\n");
aes_gcm_encrypt("",files_content[curr_file_idx]);
printf("encrypt complete\n");
// strcpy( files_content[ curr_file_idx ], "" );
}
```
在新增檔案時會執於`aes256_encrypt.c`中實作的file_metadata_init(),來產生專屬於此檔案的aes256 key與加密初始化向量。
### write_to_file()
```c=89
void write_to_file( const char *path, const char *new_content )
{
int file_idx = get_file_index( path );
if ( file_idx == -1 ) // No such file
return;
printf("Write content unencrypt:\n%s\n",new_content);
aes_gcm_encrypt(new_content,files_content[ file_idx ]);
//strcpy( files_content[ file_idx ], new_content );
}
```
在寫入前會先print出未加密的寫入內容,呼叫於`aes256_encrypt.c`中進行實作的`aes_gcm_encrypt()`進行檔案的加密後儲存。
### do_read()
```c=145
static int do_read( const char *path, char *buffer, size_t size, off_t offset, struct fuse_file_info *fi )
{
int file_idx = get_file_index( path );
if ( file_idx == -1 )
return -1;
char * content;
aes_gcm_decrypt(files_content[ file_idx ],&content);
printf("\noffset = %d, size = %d\n",(int)offset,(int)size);
memcpy( buffer, content + offset, size );
size_t return_value = strlen( content ) - offset;
free(content);
return return_value;
}
```
透過先找出file於儲存陣列中的index後,呼叫於`aes256_encrypt.c`中進行實作的`aes_gcm_decrypt()`對儲存的資料記進行解密後,將解密得到的content copy至目標buffer中。
### static int do_rmdir()
```c=198
static int do_rmdir(const char* path){
int dir_index = get_dir_index(path);
memcpy(dir_list[dir_index],dir_list[curr_dir_idx],sizeof(dir_list[0]));
curr_dir_idx = curr_dir_idx-1;
return 0;
}
```
在進行刪除dir時,將list最末的dir資料copy至預刪除的index位置,並將當前dir總數量減一來達成刪除的效果。
## aes256_encrypt.c
:::spoiler aes256_encrypt.c
```c=1
#include <stdio.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/core_names.h>
#include <time.h>
#include <string.h>
#define BUFFER_SIZE 1000
#define DEBUG
//struct used for file content.
typedef struct {
char *content;
int size;
unsigned char key[32];
unsigned char iv[12];
}file_metadata;
//print hex data stored in char data type.
int print_data(const char *start_ptr,int data_len,char*data_name){
#ifdef DEBUG
printf("\n%s\n",data_name);
for(int i = 0;i<data_len;i++){
printf("%x ",(unsigned char)start_ptr[i]);
}
printf("\n");
#endif
}
int random_key_gen(unsigned char* random256key,int byte_num){
for(int i = 0;i<byte_num;i++){
random256key[i] = rand()&0xFF;
}
#ifdef DEBUG
printf("New file key generated.\n");
#endif
}
file_metadata* file_metadata_init(){
file_metadata *data = (file_metadata*)malloc(sizeof(file_metadata));
random_key_gen(data->key, 32);
random_key_gen(data->iv,12);
data ->content = NULL;
data->size = 0;
print_data(data->key,32,"New file key:");
return data;
}
int file_metadata_free(file_metadata* data){
free(data->content);
free(data);
}
#ifdef AES_FUNCTION_TEST
/* Example plaintext to encrypt 64 */
static unsigned char gcm_pt[] = {
0xf5, 0x6e, 0x87, 0x05, 0x5b, 0xc3, 0x2d, 0x0e, 0xeb, 0x31, 0xb2, 0xea,
0xcc, 0x2b, 0xf2, 0xa5,'\0'
};
#endif
file_metadata* aes_gcm_encrypt(const char *input,file_metadata* data){
if(strlen(input)!=0){
//initial setting reference from openssl aes demo
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int outlen,tmplen;
// unsigned char outbuf[BUFFER_SIZE];
unsigned char* outbuf = (char*)calloc(strlen(input),sizeof(char));
OSSL_PARAM params[2] = {
OSSL_PARAM_END, OSSL_PARAM_END
};
size_t gcm_ivlen = sizeof(data->iv);
print_data(input,strlen(input),"Write content unencrypt(Hex):");
/* Set IV length if default 96 bits is not appropriate */
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN,
&gcm_ivlen);
cipher = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
ctx = EVP_CIPHER_CTX_new();
// aes encryption
EVP_EncryptInit_ex2(ctx, cipher, data->key, data->iv, params);
EVP_EncryptUpdate(ctx, outbuf, &outlen, input, strlen(input));
EVP_EncryptFinal_ex(ctx, outbuf, &tmplen);
outlen+=tmplen;
//saving encrypted content and size.
if(data->content != NULL){
free(data->content);
data->content = NULL;
}
data->content = (char*)calloc(outlen,sizeof(char));
memcpy(data->content,outbuf,(int)outlen);
free(outbuf);
data->size = (int)outlen;
print_data(data->content,data->size,"Write content encrypted(Hex)");
EVP_CIPHER_free(cipher);
EVP_CIPHER_CTX_free(ctx);
}
return data;
}
//unsigned char* gcm_ct
void aes_gcm_decrypt(file_metadata * data,char** output){
if(data->size!= 0){
//initial setting reference from openssl aes demo
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int outlen;
int total_outlen;
size_t gcm_ivlen = sizeof(data->iv);
// unsigned char outbuf[BUFFER_SIZE];
unsigned char* outbuf = (char*)calloc(data->size,sizeof(char));
OSSL_PARAM params[2] = {
OSSL_PARAM_END, OSSL_PARAM_END
};
print_data(data->content,strlen(data->content),"File content undecrypt(Hex):");
/* Set IV length if default 96 bits is not appropriate */
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN,
&gcm_ivlen);
cipher = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
ctx = EVP_CIPHER_CTX_new();
// aes decryption
EVP_DecryptInit_ex2(ctx, cipher, data->key, data->iv, params);
EVP_DecryptUpdate(ctx, outbuf, &outlen, data->content, data->size);
total_outlen = outlen;
EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
total_outlen += outlen;
print_data(outbuf,total_outlen,"File content decyrpted(Hex):");
*output = (char*)calloc(((total_outlen+1)/4096+1)*4096,sizeof(char));
memcpy(*output,outbuf,total_outlen);
free(outbuf);
(*output) [total_outlen] = '\0';
EVP_CIPHER_free(cipher);
EVP_CIPHER_CTX_free(ctx);
}else{
*output = (char*)calloc(1,sizeof(char));
*output[0] = '\0';
}
}
#ifdef AES_FUNCTION_TEST
int main(){
char size_test[] ="";
printf("size of \"\" = %d\n",(int)sizeof(size_test));
char *decryped_data;
srand(time(NULL));
//char random256key[32];
char *test_data = "t";
char *test_data2 = "test enc_final";
printf("\n strlen of \"\":%d\n",(int)strlen(test_data));
//create new file_metadata
file_metadata *data = file_metadata_init();
aes_gcm_encrypt(test_data,data);
print_data(data->content,data->size,"main_check cipher_text:");
printf("\ncypher strlen:%d\n",(int)strlen(data->content));
char *output;
aes_gcm_decrypt(data,&output);
printf("\ndescrypt strlen:%d\n",(int)strlen(output));
print_data(output,strlen(output),"output_test");
// print_data(plain_text->content,plain_text->size,"main_check plain text");
//printf("\n main_check decrypted data%s",decryped_data);
printf("\nstring test:%s\n",output[1]);
// file_metadata_free(cipher_data);
printf("\nsize of test = %d\n",sizeof(*output));
free(output);
}
#endif
```
:::
### sturct file_metadata
```c=11
//struct used for file content.
typedef struct {
char *content;
int size;
unsigned char key[32];
unsigned char iv[12];
}file_metadata;
```
每個`file_metadata struct` instance會對應一個file,`file_metadata struct`包含五個元素,分別紀錄指向content的pointer、content size、專屬於這個file的aes256 key(32 Byte = 256bit)、專屬於這個檔案的加密初始化向量iv。
### int random_key_gen(unsigned char* random256key,int byte_num)
```c=31
int random_key_gen(unsigned char* random256key,int byte_num){
for(int i = 0;i<byte_num;i++){
random256key[i] = rand()&0xFF;
}
#ifdef DEBUG
printf("New file key generated.\n");
#endif
}
```
在初始化file metadata(create新file)時,會為這個檔案產生一個新的aes256key(另外也用於產生16 Byte的初始化向量),使用者可以指定要產生的Byte長度,function中透過`rand()&0xFF`來每次產生一個Byte長度的隨機bit串。
### file_metadata* file_metadata_init()
```c=41
file_metadata* file_metadata_init(){
file_metadata *data = (file_metadata*)malloc(sizeof(file_metadata));
random_key_gen(data->key, 32);
random_key_gen(data->iv,12);
data ->content = NULL;
data->size = 0;
print_data(data->key,32,"New file key:");
return data;
}
```
在每一次create新檔案時都會對應新建一個`file_metadata`實例,並對該`file_metadata`實例中的4個元素分別進行初始化(ex:生成專屬於這個新檔案用於aes256加密的key、初始vector)。
### file_metadata* aes_gcm_encrypt(const char *input,file_metadata* data)
```c=66
file_metadata* aes_gcm_encrypt(const char *input,file_metadata* data){
if(strlen(input)!=0){
//initial setting reference from openssl aes demo
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int outlen,tmplen;
// unsigned char outbuf[BUFFER_SIZE];
unsigned char* outbuf = (char*)calloc(strlen(input),sizeof(char));
OSSL_PARAM params[2] = {
OSSL_PARAM_END, OSSL_PARAM_END
};
size_t gcm_ivlen = sizeof(data->iv);
print_data(input,strlen(input),"Write content unencrypt(Hex):");
/* Set IV length if default 96 bits is not appropriate */
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN,
&gcm_ivlen);
cipher = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
ctx = EVP_CIPHER_CTX_new();
// aes encryption
EVP_EncryptInit_ex2(ctx, cipher, data->key, data->iv, params);
EVP_EncryptUpdate(ctx, outbuf, &outlen, input, strlen(input));
EVP_EncryptFinal_ex(ctx, outbuf, &tmplen);
outlen+=tmplen;
//saving encrypted content and size.
if(data->content != NULL){
free(data->content);
data->content = NULL;
}
data->content = (char*)calloc(outlen,sizeof(char));
memcpy(data->content,outbuf,(int)outlen);
free(outbuf);
data->size = (int)outlen;
print_data(data->content,data->size,"Write content encrypted(Hex)");
EVP_CIPHER_free(cipher);
EVP_CIPHER_CTX_free(ctx);
}
return data;
}
```
此function會透過傳入的`file_metadata` instance `data`中的`key`與`iv`來對傳入的`char* input`內容進行aes256加密,並將加密後的資料存入傳入的`file_metatdata`instant(pointer) `data`的`content`欄位(pointer)中。
首先檢查若input長度為0,那就跳過加密,
`68~86`:初始化openssl的aes加密功能。
`89~91`:進行Aes加密
`94~104`:將加密後content存入`file metadata data`中。
### void aes_gcm_decrypt(file_metadata * data,char** output)
```c=114
void aes_gcm_decrypt(file_metadata * data,char** output){
if(data->size!= 0){
//initial setting reference from openssl aes demo
EVP_CIPHER_CTX *ctx;
EVP_CIPHER *cipher = NULL;
int outlen;
int total_outlen;
size_t gcm_ivlen = sizeof(data->iv);
// unsigned char outbuf[BUFFER_SIZE];
unsigned char* outbuf = (char*)calloc(data->size,sizeof(char));
OSSL_PARAM params[2] = {
OSSL_PARAM_END, OSSL_PARAM_END
};
print_data(data->content,strlen(data->content),"File content undecrypt(Hex):");
/* Set IV length if default 96 bits is not appropriate */
params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_AEAD_IVLEN,
&gcm_ivlen);
cipher = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL);
ctx = EVP_CIPHER_CTX_new();
// aes decryption
EVP_DecryptInit_ex2(ctx, cipher, data->key, data->iv, params);
EVP_DecryptUpdate(ctx, outbuf, &outlen, data->content, data->size);
total_outlen = outlen;
EVP_DecryptFinal_ex(ctx, outbuf, &outlen);
total_outlen += outlen;
print_data(outbuf,total_outlen,"File content decyrpted(Hex):");
*output = (char*)calloc(((total_outlen+1)/4096+1)*4096,sizeof(char));
memcpy(*output,outbuf,total_outlen);
free(outbuf);
(*output) [total_outlen] = '\0';
EVP_CIPHER_free(cipher);
EVP_CIPHER_CTX_free(ctx);
}else{
*output = (char*)calloc(1,sizeof(char));
*output[0] = '\0';
}
}
```
此function會透過傳入的`file_metadata` instance `data`中的`key`與`iv`來對`content`進行AES-256解密。
`115`、`150~153`:`aes_gcm_decrypt`會先檢查傳入的`file_metadata` instance `data`長度不為空,若為空則回傳1 Byte 的 `\0` char 代表檔案結尾。
`117~133`初始化openssl的AES-256 decrypt功能。
`134~140`進行AES-256 decryption。
`141~146`將解密後的內容存入output buffer中。
# 基本操作
## 編譯及掛載

## Create, read, and write files.
### Create file


### write file


### read file


## Open and close files.
### Open

### Close(Release)

## Create and remove directories.
### Create dir

### Remove dir

## List directory contents.

# Part 3,4,5,6 AES加解密:
## 每個檔案使用不同的Key加密與解密、能於OPEN時正確獲得對應KEY,並且在所有基礎操作中都能正確進行加密與解密:






## 驗證沒有正確的Key,檔案的內容unreadable:
透過新增的程式碼片段,強制將解密金鑰改為錯誤的金鑰:

可以看到測試Read到的資料為亂碼:

將`#define USE_INCORRECT_KEY`移除後,可以看到如下圖,解密回復正常。

# 碰到問題
在讀取檔案時出現segemtation fault,
core dumped.
後來發現是測試檔案的大小太大,超出了設定的]
fuse_operations中operations中的.read(do_read函數的傳入參數中)



一次讀取的size最小為4096,因此若使用malloc或calloc分配記憶體來佔存輸出的資料時,若只分配剛好與檔案內容大小相等的記憶體(不足4096Byte),在memcopy容易會觸發segmentation error.
