日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Node.js結(jié)合MongoDB實(shí)現(xiàn)字段級(jí)自動(dòng)加密

某些場(chǎng)景下,對(duì)于數(shù)據(jù)隱私會(huì)有較高的要求,例如,用戶系統(tǒng)的個(gè)人信息(身份證、手機(jī)號(hào))、醫(yī)患系統(tǒng)的患者信息等,怎么用技術(shù)手段安全的保護(hù)這些敏感數(shù)據(jù)是我們開(kāi)發(fā)人員需要考慮的問(wèn)題。

創(chuàng)新互聯(lián)公司專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站建設(shè)、成都網(wǎng)站制作、大冶網(wǎng)絡(luò)推廣、微信小程序開(kāi)發(fā)、大冶網(wǎng)絡(luò)營(yíng)銷、大冶企業(yè)策劃、大冶品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營(yíng)等,從售前售中售后,我們都將竭誠(chéng)為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)公司為所有大學(xué)生創(chuàng)業(yè)者提供大冶建站搭建服務(wù),24小時(shí)服務(wù)熱線:13518219792,官方網(wǎng)址:www.cdcxhl.com

本篇文章,將介紹 MongoDB 的客戶端字段級(jí)加密功能,英文全稱為 Client-Side Field Level Encryption,在有些地方會(huì)看到簡(jiǎn)稱為 CSFLE,代表的是一個(gè)意思,下文有些地方也會(huì)這樣稱呼。

該功能允許開(kāi)發(fā)人員將數(shù)據(jù)保存到 MongoDB 服務(wù)器之前選擇性的指定數(shù)據(jù)字段進(jìn)行加密,這些加密/解密操作都是事先在客戶端完成,與服務(wù)器通信時(shí)完全是加密的,最終只有配置了 CSFLE 客戶端才能讀取和寫(xiě)入敏感數(shù)據(jù)字段。

文末列舉了幾個(gè)使用中的常見(jiàn)錯(cuò)誤原因,如有遇到類似錯(cuò)誤可以做為參考。

環(huán)境要求

MongoDB Server 選擇:MongoDB 客戶端字段級(jí)加密分為自動(dòng)加密、手動(dòng)加密兩種類型,自動(dòng)加密社區(qū)版是不支持的,需要 MongoDB Server 4.2 企業(yè)版 或 MongoDB Atlas,學(xué)習(xí)使用推薦 MongoDB Atlas,它是在云服務(wù)器中托管的 MongoDB 服務(wù)器,不需要安裝,且提供了免費(fèi)的入門(mén)套餐是夠我們學(xué)習(xí)使用了。

驅(qū)動(dòng)兼容性:使用支持 CSFLE 功能的 Node.js MongoDB 驅(qū)動(dòng)程序,3.4+ 以上版本是支持的,快速入門(mén)。

libmongocrypt:客戶端字段級(jí)加密依賴 libmongocrypt,它是 MongoDB 驅(qū)動(dòng)程序?qū)崿F(xiàn)客戶端加密/解密的核心組件,對(duì)應(yīng)的 Node.js NPM 包為 mongodb-client-encryption,需要注意這個(gè)包依賴于 libbson 和 libmongocrypt C 庫(kù),需要 C++ 工具鏈,但是做為 Node.js Addons 插件,其已經(jīng)利用 prebuild 在 CI 期間做了模塊的預(yù)先編譯,直接 npm i mongodb-client-encryption 安裝即可,如果網(wǎng)絡(luò)環(huán)境問(wèn)題鏈接不上 github.com 可能就很麻煩了需要手動(dòng)構(gòu)建、編譯,因?yàn)閷?duì)模塊的預(yù)先編譯是放在 Github 上的。

mongocryptd:客戶端加密必須要 mongocryptd 進(jìn)程啟動(dòng)才能正常工作,剛開(kāi)始一直遇到一個(gè)問(wèn)題:MongoError: BSON field 'insert.jsonSchema' is an unknown field. This command may be meant for a mongocryptd process. 貌似就是因?yàn)?mongocryptd 進(jìn)程沒(méi)有啟動(dòng)導(dǎo)致的。在 MongoDB Server 企業(yè)版中包含 mongocryptd 這個(gè)組件的,解決辦法也很簡(jiǎn)單就是本機(jī)安裝下企業(yè)版,盡管我們這里使用的是 MongoDB Atlas 也要安裝的,安裝方法參考 docs.mongodb.com/manual/tutorial/install-mongodb-enterprise-on-os-x。

項(xiàng)目準(zhǔn)備

做一些初始化工作,安裝依賴、配置文件、創(chuàng)建一個(gè)常規(guī)的 MongoDB client。

項(xiàng)目初始化

mkdir nodejs-mongodb-client-encryption
cd nodejs-mongodb-client-encryption
npm init
npm i mongodb mongodb-client-encryption -S

配置文件

創(chuàng)建一個(gè) index.js 文件,核心代碼邏輯都在該文件編寫(xiě),

// index.js
const base64 = require('uuid-base64');
const { MongoClient, Binary } = require('mongodb');
const { ClientEncryption } = require('mongodb-client-encryption');
const fs = require('fs');

// 配置
const config = {
connectionString: '${替換為自己的 MongoDB 鏈接字符串}',
keyVaultDb: 'encryption', // encryption 表示密鑰保管數(shù)據(jù)庫(kù)
keyVaultCollection: '__keyVault', // __keyVault 表示集合
keyVaultNamespace: `encryption.__keyVault`, // 密鑰庫(kù)命名空間
keyAltNames: 'test-data-key',
masterKeyPath: 'master-key.txt'
}
const LOCAL_MASTER_KEY = fs.readFileSync(config.masterKeyPath); // 讀取本地主密鑰
const kmsProviders = { // 指定 KMS 提供程序設(shè)置
local: {
key: LOCAL_MASTER_KEY,
},
};

創(chuàng)建常規(guī)

/**
* 獲取常規(guī) Mongo 客戶端
* @param {String} connectionString
* @returns
*/
function getRegularClient(connectionString) {
const client = new MongoClient(connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
});

return client.connect();
}

數(shù)據(jù)加密密鑰

MongoDB 驅(qū)動(dòng)程序自動(dòng)加密/解密時(shí)需要訪問(wèn)事先創(chuàng)建的數(shù)據(jù)加密密鑰,而這個(gè)密鑰經(jīng)過(guò)程序的處理會(huì)存儲(chǔ)在密鑰保管數(shù)據(jù)庫(kù)的集合中,以下是創(chuàng)建一個(gè)數(shù)據(jù)加密密鑰的交互圖。

創(chuàng)建主密鑰

創(chuàng)建 MongoDB 數(shù)據(jù)加密密鑰還需要另外一個(gè)稱為 “主密鑰” 的密鑰進(jìn)行加密,下圖展示了創(chuàng)建主密鑰的流程:

主密鑰的存儲(chǔ),生產(chǎn)環(huán)境 MongoDB 官方的推薦是使用密鑰管理服務(wù)(KMS):亞馬遜網(wǎng)絡(luò)服務(wù) KMS、Azure 密鑰保管庫(kù)、谷歌云平臺(tái)密鑰管理,更多內(nèi)容可閱讀 客戶端字段級(jí)加密:使用 KMS 存儲(chǔ)主密鑰。

學(xué)習(xí)為目的,簡(jiǎn)單方便些可使用本地密鑰提供程序存儲(chǔ)主密鑰,這種方式不安全,不適合生產(chǎn)。

創(chuàng)建一個(gè)腳本文件 create-master-key.js,生成一個(gè) 96 字節(jié)的密鑰文件,并寫(xiě)入到本地文件系統(tǒng)的 master-key.txt 文件中。

// create-master-key.js
const fs = require('fs');
const crypto = require('crypto');

try {
fs.writeFileSync('master-key.txt', crypto.randomBytes(96));
} catch (err) {
console.error(err);
}

指定 KMS 程序配置

客戶端使用如下配置發(fā)現(xiàn)主密鑰,local 表示的是使用本地主密鑰。

const LOCAL_MASTER_KEY = fs.readFileSync(config.masterKeyPath); // 讀取本地主密鑰
const kmsProviders = { // 指定 KMS 提供程序設(shè)置
local: {
key: LOCAL_MASTER_KEY,
},
};

獲取或創(chuàng)建數(shù)據(jù)加密密鑰寫(xiě)一個(gè)函數(shù) getOrCreateDataKey 分別傳入創(chuàng)建的常規(guī) client、上面指定的 KMS 程序配置,該方法目的是獲取一個(gè)數(shù)據(jù)密鑰,如果不存在則創(chuàng)建,實(shí)現(xiàn)為以下幾個(gè)步驟:

  • 在密鑰保管庫(kù)集合的 keyAltNames 字段上先設(shè)置唯一索引,這里創(chuàng)建的是一個(gè)部分索引,符合條件的才會(huì)創(chuàng)建。
  • 檢查是否已創(chuàng)建數(shù)據(jù)加密密鑰,若創(chuàng)建則立即返回。
  • 若未創(chuàng)建數(shù)據(jù)加密密鑰,向指定的密鑰保管庫(kù)集合創(chuàng)建一條新的數(shù)據(jù)密鑰。
/**
* 獲取或創(chuàng)建數(shù)據(jù)加密密鑰
* 如果已存在 dataKey 則返回,否則創(chuàng)建一條 dataKey
*/
async function getOrCreateDataKey(regularClient, kmsProviders) {
// 在密鑰保管庫(kù)集合的 keyAltNames 字段上先設(shè)置索引
await regularClient
.db(config.keyVaultDb)
.collection(config.keyVaultCollection)
.createIndex("keyAltNames", {
unique: true,
partialFilterExpression: {
keyAltNames: {
$exists: true
}
}
});

// 檢查是否已創(chuàng)建數(shù)據(jù)加密密鑰
const dataKeyInfo = await regularClient
.db(config.keyVaultDb)
.collection(config.keyVaultCollection)
.findOne({
keyAltNames: {
$in: [config.keyAltNames]
}
});
if (dataKeyInfo) { // 存在立即返回
return dataKeyInfo['_id'].toString("base64");
}

// 創(chuàng)建一條新的數(shù)據(jù)密鑰
const encryption = new ClientEncryption(regularClient, {
keyVaultNamespace: config.keyVaultNamespace,
kmsProviders,
});
const dataKey = await encryption.createDataKey('local', {
keyAltNames: [config.keyAltNames]
});
return dataKey.toString('base64');
}

驗(yàn)證數(shù)據(jù)加密密鑰是否成功創(chuàng)建

調(diào)用編寫(xiě)好的方法,驗(yàn)證下數(shù)據(jù)加密密鑰是否創(chuàng)建成功。

(async () => {
let regularClient;
try {
// 創(chuàng)建常規(guī) MongoDB 客戶端
regularClient = await getRegularClient(config.connectionString);
// 獲取數(shù)據(jù)加密密鑰
const base64DataKeyId = await getOrCreateDataKey(regularClient, kmsProviders);
} catch (err) {
console.error(err);
regularClient.close();
}
})();

我使用 Robo 3T 鏈接的 Atlas 集群,如果一切正常,你會(huì)看到在 encryption.__keyVault 集合中有如下一條密鑰記錄,_id 字段就是為我們需要的數(shù)據(jù)加密密鑰,使用 Base64 格式編碼。

JSON Schema 定義

Node.js 驅(qū)動(dòng)程序使用 JSON Schema 定義集合需要加密的字段,文檔類型定義使用 BSON 類型。

  • encryptMetadata.keyId:在根級(jí)別配置數(shù)據(jù)加密密鑰,properties 中的每個(gè)字段默認(rèn)都繼承該密鑰,除非特別指定,參考 docs.mongodb.com/manual/reference/security-client-side-automatic-json-schema/#encryptmetadata-schema-keyword。
  • algorithm:指定加密算法,AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic 為確定性加密算法,對(duì)讀取操作提供了更好的支持,安全系數(shù)相對(duì)沒(méi)有**隨機(jī)加密 **AEAD_AES_256_CBC_HMAC_SHA_512-Random 高,隨機(jī)加密算法每次執(zhí)行加密都會(huì)輸出不同的值。
/**
* 使用 JSON Schema 定義集合需要加密的字段
* @param {String} base64DataKeyId
* @returns
*/
function getSchemaMap(base64DataKeyId) {
// 使用 JSON Schema 指定加密字段
const userJsonSchema = {
bsonType: 'object',
encryptMetadata: {
keyId: [new Binary(Buffer.from(base64DataKeyId, 'base64'), 4)]
},
properties: {
phone: {
encrypt: {
bsonType: 'string',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
}
},
password: {
encrypt: {
bsonType: 'string',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'
}
},
emergencyContact: {
bsonType: 'object',
properties: {
phone: {
encrypt: {
bsonType: 'string',
algorithm: 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'
}
},
}
}
}
}

// 將 JSON 模式映射到集合上
const schemaMap = {
'test.users': userJsonSchema
};

return schemaMap;
}

CSFLE 客戶端驗(yàn)證讀寫(xiě)操作

在有了數(shù)據(jù)加密密鑰、JSON Schema 之后可以創(chuàng)建一個(gè)支持 CSFLE 的 Mongo client,該客戶端和 MongDB 服務(wù)器交互,讀取/寫(xiě)入帶有加密字段的數(shù)據(jù)。

讀寫(xiě)操作流程圖

下圖展示了客戶端應(yīng)用程序和驅(qū)動(dòng)程序?yàn)閷?xiě)入字段級(jí)加密數(shù)據(jù)的一個(gè)步驟:

下圖展示了客戶端應(yīng)用程序和驅(qū)動(dòng)程序?yàn)樽x取加密后字段進(jìn)行解密操作的一個(gè)過(guò)程:

創(chuàng)建 CSFLE 客戶端

創(chuàng)建 CSFLE 的 mongo client 與常規(guī) mongo client 相比較,需要多傳入 autoEncryption 對(duì)象,以下參數(shù)含義分別為:

  • keyVaultNamespace:存放數(shù)據(jù)加密密鑰的密鑰保管庫(kù)集合名稱。
  • kmsProviders:指定本地主密鑰。
  • schemaMap:需要加密字段的一些定義。
function getCSFLEClient(schemaMap, kmsProviders) {
const secureClient = new MongoClient(config.connectionString, {
useNewUrlParser: true,
useUnifiedTopology: true,
monitorCommands: true,
autoEncryption: {
bypassAutoEncryption: true,
keyVaultNamespace: config.keyVaultNamespace,
kmsProviders,
schemaMap
}
});

return secureClient.connect();
}

(async () => {
try {
const regularClient = await getRegularClient(config.connectionString);
const base64DataKeyId = await getOrCreateDataKey(regularClient, kmsProviders);
const schemaMap = getSchemaMap(base64DataKeyId);
const csfleClient = await getCSFLEClient(schemaMap, kmsProviders);

// 執(zhí)行讀寫(xiě)操作
} catch (err) {
console.error(err);
}
})();

執(zhí)行讀寫(xiě)操作驗(yàn)證

在擁有 CSFLE 客戶端后,執(zhí)行一些讀寫(xiě)操作,創(chuàng)建一條用戶記錄,下面的代碼和我們常規(guī)的讀寫(xiě)操作沒(méi)什么區(qū)別,并且 phone 這個(gè)字段雖然是經(jīng)過(guò)加密的,我們?nèi)钥墒褂迷撟侄巫鰹樗饕?查找數(shù)據(jù)。

(async () => {
try {
// ...
const db = csfleClient.db('test');
const userColl = db.collection('users');
const doc = {
name: '小張',
phone: '18800030009',
password: '123456',
emergencyContact: {
name: '小李',
phone: '16600260023'
}
};
const query = { phone: doc.phone };
await userColl.updateOne(query, { $set: doc }, { upsert: true });
const result = await userColl.findOne(query);
console.log(result);
} catch (err) {
console.error(err);
}
})();

當(dāng)成功插入一條記錄之后,在 Robo 3T 工具查詢?cè)摷?,可以看到需要的字段都已?jīng)做了加密,盡管我是一個(gè)管理員能夠查看數(shù)據(jù),也無(wú)法查看這些隱私數(shù)據(jù)。

只能通過(guò)程序正確的創(chuàng)建了 CSFLE 的客戶端才能讀取出解密后的數(shù)據(jù)。

幾個(gè)常見(jiàn)錯(cuò)誤

文中示例測(cè)試時(shí)常見(jiàn)的幾個(gè)錯(cuò)誤,可以做為參考。

認(rèn)證失敗

遇到 Authentication failed 錯(cuò)誤,基本上都是連接字符串的賬號(hào)密碼或權(quán)限錯(cuò)誤,使用 MongoDB Atlas 的需要檢查下數(shù)據(jù)庫(kù)的訪問(wèn)權(quán)限配置

MongoServerError: bad auth : Authentication failed.
...
ok: 0,
code: 8000,
codeName: 'AtlasError',
[Symbol(errorLabels)]: Set(0) {}
}

創(chuàng)建加密客戶端鏈接失敗

下面的報(bào)錯(cuò)很簡(jiǎn)單就是服務(wù)器鏈接不上。需要注意的是文中創(chuàng)建加密客戶端還會(huì)去鏈接本地安裝的 MongoDB 企業(yè)版 Server,在本地啟動(dòng) MongoDB 企業(yè)版 Server 時(shí)需要指定下端口 bin/mongod --dbpath data --logpath logs/mongo.log --port 27020。

MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27020
at Timeout._onTimeout (/Users/***********/nodejs-mongodb-client-encryption/node_modules/mongodb/lib/sdam/topology.js:318:38)
at listOnTimeout (internal/timers.js:554:17)
at processTimers (internal/timers.js:497:7) {
reason: TopologyDescription {
type: 'Unknown',
servers: Map(1) { 'localhost:27020' => [ServerDescription] },
stale: false,
compatible: true,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
logicalSessionTimeoutMinutes: undefined
},
code: undefined,
[Symbol(errorLabels)]: Set(0) {}
}

mongocryptd 進(jìn)程注意事項(xiàng)

在剛開(kāi)始的環(huán)境要求里有提到過(guò) mongocryptd 進(jìn)程,它會(huì)在這里檢查 JSON Schema 中定義的加密指令,也就是 getCSFLEClient() 傳入的 schemaMap 參數(shù),如果 mongocryptd 進(jìn)程沒(méi)有啟動(dòng),這里會(huì)一直報(bào)錯(cuò)。

以下是我最開(kāi)始一直遇到的一個(gè)問(wèn)題,解決辦法很簡(jiǎn)單:

  • 第一步,本機(jī)安裝下企業(yè)版
  • 第二步,創(chuàng)建加密的 MongoDB 客戶端時(shí),鏈接參數(shù)要設(shè)置autoEncryption.bypassAutoEncryption=true 會(huì)自動(dòng)生成 mongocryptd 進(jìn)程。
writeError occurred: MongoError: BSON field 'insert.jsonSchema' is an unknown field. This command may be meant for a mongocryptd process.
at MessageStream.messageHandler (/Users/quzhenfei/Documents/study/node_modules/mongodb/lib/cmap/connection.js:268:20)
at MessageStream.emit (events.js:314:20)
at processIncomingData (/Users/quzhenfei/Documents/study/node_modules/mongodb/lib/cmap/message_stream.js:144:12)
at MessageStream._write (/Users/quzhenfei/Documents/study/node_modules/mongodb/lib/cmap/message_stream.js:42:5)
at writeOrBuffer (_stream_writable.js:352:12)
at MessageStream.Writable.write (_stream_writable.js:303:10)
at TLSSocket.ondata (_stream_readable.js:713:22)
at TLSSocket.emit (events.js:314:20)
at addChunk (_stream_readable.js:303:12)
at readableAddChunk (_stream_readable.js:279:9) {
operationTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1632613160 },
ok: 0,
code: 4662500,
codeName: 'Location4662500',
'$clusterTime': {
clusterTime: Timestamp { _bsontype: 'Timestamp', low_: 1, high_: 1632613160 },
signature: { hash: [Binary], keyId: [Long] }
}
}

總結(jié)

MongoDB 提供的客戶端字段級(jí)自動(dòng)加密,對(duì)于有數(shù)據(jù)隱私需要加密保護(hù)的還是很方便的,在配置了 CSFLE 客戶端后應(yīng)用程序在讀寫(xiě)操作時(shí)和常規(guī)的客戶端讀寫(xiě)操作是沒(méi)有差別的,唯一的阻礙可能是僅企業(yè)版支持。

文中我們將主密鑰存儲(chǔ)放在了本地的文件系統(tǒng)中,這在本地測(cè)試環(huán)境是可以的,但是生產(chǎn)環(huán)境不要用這種方式,因?yàn)槿魏文軌蛟L問(wèn)您本地文件系統(tǒng)主密鑰的人都可以讀取您的數(shù)據(jù)加密密鑰,建議放在更安全的地方,例如密鑰管理系統(tǒng)(KMS)。

Reference

  • docs.mongodb.com/drivers/security/client-side-field-level-encryption-guide/#e.-perform-encrypted-read-write-operations
  • 基于 Mongo Shell 的手動(dòng)加密 https://www.modb.pro/db/100877
  • www.mongodb.com/community/forums/t/fle-mongoerror-bson-field-insert-jsonschema-is-an-unknown-field/5472/7
  • www.mongodb.com/developer/how-to/client-side-field-level-encryption-csfle-mongodb-node
  • mongodb.github.io/node-mongodb-native/3.4/reference/client-side-encryption/

分享文章:Node.js結(jié)合MongoDB實(shí)現(xiàn)字段級(jí)自動(dòng)加密
網(wǎng)站鏈接:http://www.5511xx.com/article/dhhpjdd.html