officecrypto-tool
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -19,2 +19,8 @@ | ||
password: string; | ||
/** | ||
* @desc Encryption Type | ||
* optional | ||
*/ | ||
type?: 'standard'; | ||
} | ||
@@ -21,0 +27,0 @@ |
31
index.js
@@ -17,3 +17,10 @@ /* eslint-disable valid-jsdoc */ | ||
async function decrypt(input, options) { | ||
if (!Buffer.isBuffer(input)) throw new Error('The input must be a buffer'); | ||
if (!Buffer.isBuffer(input)) { | ||
// This is an ArrayBuffer in the browser. Convert to a Buffer. | ||
if (ArrayBuffer.isView(input)) { | ||
input = Buffer.from(input); | ||
} else { | ||
throw new Error('The input must be a buffer'); | ||
} | ||
} | ||
if (!options || !options.password) throw new Error('options.password is required'); | ||
@@ -24,3 +31,3 @@ | ||
if (encryptionInfo) { // 这个是xlsx格式的加密 | ||
if (encryptionInfo) { // This is encrypted in xlsx format | ||
const encryptedPackage = CFB.find(cfb, '/EncryptedPackage'); | ||
@@ -69,3 +76,10 @@ const einfo = common.parseEncryptionInfo(encryptionInfo.content); | ||
function encrypt(input, options) { | ||
if (!Buffer.isBuffer(input)) throw new Error('The input must be a buffer'); | ||
if (!Buffer.isBuffer(input)) { | ||
// This is an ArrayBuffer in the browser. Convert to a Buffer. | ||
if (ArrayBuffer.isView(input)) { | ||
input = Buffer.from(input); | ||
} else { | ||
throw new Error('The input must be a buffer'); | ||
} | ||
} | ||
if (!options || !options.password) throw new Error('options.password is required'); | ||
@@ -75,3 +89,12 @@ | ||
if (options.password.length > maxFieldLength) throw new Error(`The maximum password length is ${maxFieldLength}`); | ||
const output = ecma376Agile.encrypt(input, options.password); | ||
let output; | ||
if (options.hasOwnProperty('type') && !['standard'].includes(options.type)) { | ||
throw new Error(`options.type must be ['standard']`); | ||
} | ||
if (options.type === 'standard') { | ||
output = ecma376Standard.encryptStandard(input, options.password); | ||
} else { | ||
output = ecma376Agile.encrypt(input, options.password); | ||
} | ||
return output; | ||
@@ -78,0 +101,0 @@ } |
{ | ||
"name": "officecrypto-tool", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "officeCrypto is a library for node.js that can be used to decrypt and encrypt excel files.", | ||
@@ -15,2 +15,3 @@ "keywords": [ | ||
"private": false, | ||
"license": "MIT", | ||
"author": "zurmokeeper", | ||
@@ -17,0 +18,0 @@ "main": "index.js", |
@@ -5,4 +5,5 @@ /* eslint-disable require-jsdoc */ | ||
const crypto = require('crypto'); | ||
const cfb = require('cfb'); | ||
exports.convertPasswordToKey = function convertPasswordToKey(password, algId, algIdHash, providerType, keySize, saltSize, salt) { | ||
const convertPasswordToKey = exports.convertPasswordToKey = function convertPasswordToKey(password, algId, algIdHash, providerType, keySize, saltSize, salt) { | ||
const ITER_COUNT = 50000; | ||
@@ -46,2 +47,6 @@ const cbRequiredKeyLength = keySize / 8; | ||
exports.verifyKey = function verifyKey(key, encryptedVerifier, encryptedVerifierHash) { | ||
// In the browser environment. Convert to a Buffer. | ||
if (!Buffer.isBuffer(encryptedVerifier)) encryptedVerifier = Buffer.from(encryptedVerifier); | ||
if (!Buffer.isBuffer(encryptedVerifierHash)) encryptedVerifierHash = Buffer.from(encryptedVerifierHash); | ||
const aes = crypto.createDecipheriv('aes-128-ecb', key, Buffer.alloc(0)); | ||
@@ -64,2 +69,5 @@ aes.setAutoPadding(false); | ||
// In the browser environment. Convert to a Buffer. | ||
if (!Buffer.isBuffer(input)) input = Buffer.from(input); | ||
// The package is encoded in chunks. Encrypt/decrypt each and concat. | ||
@@ -94,3 +102,3 @@ let start = 0; let end = 0; | ||
exports.encrypt = function encrypt(key, input) { | ||
const encrypt = exports.encrypt = function encrypt(key, input) { | ||
const outputChunks = []; | ||
@@ -122,3 +130,2 @@ const offset = 0; | ||
// Concat all of the output chunks. | ||
@@ -138,1 +145,89 @@ let output = Buffer.concat(outputChunks); | ||
} | ||
function genVerifier(key) { | ||
const verifierHashInput = crypto.randomBytes(16); | ||
const aes = crypto.createCipheriv('aes-128-ecb', key, Buffer.alloc(0)); | ||
aes.setAutoPadding(false); | ||
const verifierHashInputValue = Buffer.concat([aes.update(verifierHashInput), aes.final()]); | ||
let verifierHashInputKey = crypto.createHash('sha1').update(verifierHashInput).digest(); | ||
const blockSize = 16; | ||
const remainder = verifierHashInputKey.length % blockSize; | ||
if (remainder) verifierHashInputKey = Buffer.concat([verifierHashInputKey, Buffer.alloc(blockSize - remainder)]); | ||
const aes2 = crypto.createCipheriv('aes-128-ecb', key, Buffer.alloc(0)); | ||
aes2.setAutoPadding(false); | ||
const verifierHashInputKeyValue = Buffer.concat([aes2.update(verifierHashInputKey), aes2.final()]); | ||
return {encryptedVerifier: verifierHashInputValue, encryptedVerifierHash: verifierHashInputKeyValue}; | ||
} | ||
function buildEncryptionInfo(key, keyDataSaltValue) { | ||
const blob = Buffer.alloc(224); | ||
cfb.utils.prep_blob(blob, 0); | ||
blob.write_shift(2, 0x0004); | ||
blob.write_shift(2, 0x0002); | ||
blob.write_shift(4, 0x24); // EncryptionHeaderFlags | ||
blob.write_shift(4, 0x8c); // 140 EncryptionHeaderSize | ||
blob.write_shift(4, 0x24); // Flags | ||
blob.write_shift(4, 0x00); // SizeExtra | ||
blob.write_shift(4, 0x660E); // AlgID | ||
blob.write_shift(4, 0x8004); // AlgIDHash | ||
blob.write_shift(4, 0x80); // KeySize 128 | ||
blob.write_shift(4, 0x18); // ProviderType; | ||
blob.write_shift(4, 0x00); // Reserved1 | ||
blob.write_shift(4, 0x00); // Reserved2 | ||
// The entire EncryptionHeaderSize is 140 bytes, the above is already 32 bytes, leaving 108 bytes, since it is utf16le | ||
// so providerName = 108/2 = 54 | ||
const providerName = 'Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)'; | ||
blob.write_shift(54, providerName, 'utf16le'); | ||
blob.write_shift(4, 0x10); // SaltSize | ||
const {encryptedVerifier, encryptedVerifierHash} = genVerifier(key); | ||
blob.write_shift(16, keyDataSaltValue.toString('hex'), 'hex'); // Salt | ||
blob.write_shift(16, encryptedVerifier.toString('hex'), 'hex'); // EncryptedVerifier | ||
blob.write_shift(4, 0x14); // VerifierHashSize | ||
blob.write_shift(32, encryptedVerifierHash.toString('hex'), 'hex'); // EncryptedVerifierHash | ||
return blob; | ||
} | ||
function buildEncryptionPackage(key, input) { | ||
const output = encrypt(key, input); | ||
return output; | ||
} | ||
exports.encryptStandard = function encryptStandard(input, password) { | ||
// Create a new CFB | ||
let output = cfb.utils.cfb_new(); | ||
const KeySize = 128; | ||
const AlgID = 0x660E; | ||
const AlgIDHash = 0x8004; | ||
const ProviderType = 0x18; | ||
const saltSize = 16; | ||
const keyDataSaltValue = crypto.randomBytes(16); | ||
const key = convertPasswordToKey(password, AlgID, AlgIDHash, ProviderType, KeySize, saltSize, keyDataSaltValue); | ||
const encryptionInfoBuffer = buildEncryptionInfo(key, keyDataSaltValue); | ||
const encryptedPackage = buildEncryptionPackage(key, input); | ||
// Add the encryption info and encrypted package | ||
cfb.utils.cfb_add(output, 'EncryptionInfo', encryptionInfoBuffer); | ||
cfb.utils.cfb_add(output, 'EncryptedPackage', encryptedPackage); | ||
// Delete the SheetJS entry that is added at initialization | ||
cfb.utils.cfb_del(output, '\u0001Sh33tJ5'); | ||
// Write to a buffer and return | ||
output = cfb.write(output); | ||
// The cfb library writes to a Uint8array in the browser. Convert to a Buffer. | ||
if (!Buffer.isBuffer(output)) output = Buffer.from(output); | ||
return output; | ||
}; |
@@ -7,8 +7,34 @@ | ||
const filePath = './tests/data/decrypt'; | ||
const filePath = './tests/data/encrypt'; | ||
describe('ecma376_standard encrypt', () => { | ||
it('encrypt', async () => { | ||
expect(200).toEqual(200); | ||
const input = await fs.readFile(`${filePath}/standard_wait_for_encrypt.xlsx`); | ||
const output = officeCrypto.encrypt(input, {password: '123456', type: 'standard'}); | ||
await fs.writeFile(`${filePath}/standard_encrypt_finish.xlsx`, output); | ||
// expect(200).toEqual(200); | ||
}); | ||
it(`options.type must be ['standard'], options.type= ''`, async () => { | ||
const test = function test() { | ||
return async function() { | ||
const input = await fs.readFile(`${filePath}/standard_wait_for_encrypt.xlsx`); | ||
const output = officeCrypto.encrypt(input, {password: '123456', type: ''}); | ||
}; | ||
}; | ||
await expect(test()).rejects.toThrowError(new Error( `options.type must be ['standard']` )); | ||
}); | ||
it(`options.type must be ['standard'], options.type= 'xx'`, async () => { | ||
const test = function test() { | ||
return async function() { | ||
const input = await fs.readFile(`${filePath}/standard_wait_for_encrypt.xlsx`); | ||
const output = officeCrypto.encrypt(input, {password: '123456', type: 'xx'}); | ||
}; | ||
}; | ||
await expect(test()).rejects.toThrowError(new Error( `options.type must be ['standard']` )); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
165019
29
1218