ncrypt-js 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -2
- package/dist/src/ncrypt.d.ts +53 -1
- package/dist/src/ncrypt.js +113 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# NcryptJs
|
|
2
2
|
|
|
3
|
-

|
|
4
|
+
[](https://coveralls.io/github/ajimae/ncrypt-js) 
|
|
4
5
|
|
|
5
6
|
[](https://github.com/ajimae/ncrypt-js/releases) [](https://github/languages/code-size/ajimae/ncrypt-js) [](https://github.com/ajimae/ncrypt-js/issues) [](https://www.npmjs.com/package/ncrypt-js/v/2.0.0#license)
|
|
6
7
|
|
|
@@ -16,8 +17,9 @@
|
|
|
16
17
|
* [NcryptJs Methods](#ncryptjs-methods)
|
|
17
18
|
* [Using the `randomString()` methods](#using-randomstring-method)
|
|
18
19
|
* [Using `encrypt()` and `decrypt()` methods](#using-encrypt-and-decrypt-methods)
|
|
19
|
-
* [
|
|
20
|
+
* [String Encryption](#string-encryption)
|
|
20
21
|
* [Object Encryption](#object-encryption)
|
|
22
|
+
* [Using password hashing methods](#using-password-hashing-methods)
|
|
21
23
|
* [Built With](#built-with)
|
|
22
24
|
* [Contribution](#contribution)
|
|
23
25
|
* [Version Management](#version-management)
|
|
@@ -79,6 +81,9 @@ var { ncrypt } = require("ncrypt-js");
|
|
|
79
81
|
| Methods | Description | Parameters | Return |
|
|
80
82
|
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
81
83
|
| [_static_] **randomString()** | Random String. |**size**: _number_ - An optional size of the generated `randomBytes`. <br/>**enc:** _base64/hex_ - Encoding used for encoding the `randomBytes` defaults to _`base64`_ |**encoded**: _string_ - encoded string. |
|
|
84
|
+
| [_static_] **generate()** | Generates a hashed password. |**password**: _string_ - The password to hash. <br/>**options**: _object_ - Optional configuration object (see below). |**hashedPassword**: _string_ - The hashed password string. |
|
|
85
|
+
| [_static_] **verify()** | Verifies a password against a hashed password. | **password**: _string_ - The password to verify. <br/>**hashedPassword**: _string_ - The hashed password to verify against. <br/>**options**: _object_ - Optional configuration object (see below). | **boolean** - Returns `true` if the password matches the hash, `false` otherwise.
|
|
86
|
+
| [_static_] **isHashed()** | Checks if a string is a hashed password. | **password**: _string_ - The string to check. <br/>**options**: _object_ - Optional configuration object (see below). | **boolean** - Returns `true` if the string appears to be a hashed password, `false` otherwise.
|
|
82
87
|
| **encrypt()** | Encrypts data. |**data**: _object/string/number/boolean_ - The data to be encrypted. <br/>|**ciphered**: _string_ - encrypted data. |
|
|
83
88
|
| **decrypt()** | Decrypts the encrypted or ciphered data | **encodedData**: string - The encrypted data: _string_ to be decrypted. | **data**: _string/object/number/boolean_ - The decrypted or original data (it might be string or object, depends on the initial input data type).
|
|
84
89
|
|
|
@@ -204,6 +209,110 @@ _**NOTE:** The secret is required to decrypt the encrypted data, if the secret u
|
|
|
204
209
|
|
|
205
210
|
_Same goes for encoding, if data was encrypted using `hex` encoding format, decrypting with a `base64` encoding or other encoding format and vise versa will not work_
|
|
206
211
|
|
|
212
|
+
### Using password hashing methods
|
|
213
|
+
|
|
214
|
+
The `generate()`, `verify()`, and `isHashed()` static methods provide secure password hashing and verification functionality. These methods use HMAC (Hash-based Message Authentication Code) for password hashing.
|
|
215
|
+
|
|
216
|
+
#### Generating a hashed password
|
|
217
|
+
|
|
218
|
+
The `generate()` method creates a secure hash of a password with a randomly generated salt.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
var { ncrypt } = require('ncrypt-js'); // or import ncrypt from 'ncrypt-js'
|
|
222
|
+
|
|
223
|
+
var password = "mySecurePassword123";
|
|
224
|
+
var hashedPassword = ncrypt.generate(password);
|
|
225
|
+
console.log(hashedPassword); // sha1$abc12345$1$hashedvalue...
|
|
226
|
+
|
|
227
|
+
// signature
|
|
228
|
+
ncrypt.generate(password: string, options?: {
|
|
229
|
+
algorithm?: string; // Hash algorithm (default: 'sha1')
|
|
230
|
+
saltLength?: number; // Salt length in characters (default: 8)
|
|
231
|
+
iterations?: number; // Number of hash iterations (default: 1)
|
|
232
|
+
encoding?: 'hex' | 'base64'; // Encoding format (default: 'hex')
|
|
233
|
+
separator?: string; // Separator character (default: '$')
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Verifying a password
|
|
238
|
+
|
|
239
|
+
The `verify()` method checks if a plain text password matches a previously hashed password.
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
var { ncrypt } = require('ncrypt-js');
|
|
243
|
+
|
|
244
|
+
var password = "mySecurePassword123";
|
|
245
|
+
var hashedPassword = ncrypt.generate(password);
|
|
246
|
+
|
|
247
|
+
// Later, verify the password
|
|
248
|
+
var isValid = ncrypt.verify(password, hashedPassword);
|
|
249
|
+
console.log(isValid); // true
|
|
250
|
+
|
|
251
|
+
// signature
|
|
252
|
+
ncrypt.verify(password: string, hashedPassword: string, options?: {
|
|
253
|
+
encoding?: 'hex' | 'base64'; // Encoding format (default: 'hex')
|
|
254
|
+
separator?: string; // Separator character (default: '$')
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Checking if a string is hashed
|
|
259
|
+
|
|
260
|
+
The `isHashed()` method checks if a string appears to be a hashed password by verifying its format.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
var { ncrypt } = require('ncrypt-js');
|
|
264
|
+
|
|
265
|
+
var hashedPassword = ncrypt.generate("password123");
|
|
266
|
+
var isHashed = ncrypt.isHashed(hashedPassword);
|
|
267
|
+
console.log(isHashed); // true
|
|
268
|
+
|
|
269
|
+
var plainPassword = "password123";
|
|
270
|
+
var isHashed = ncrypt.isHashed(plainPassword);
|
|
271
|
+
console.log(isHashed); // false
|
|
272
|
+
|
|
273
|
+
// signature
|
|
274
|
+
ncrypt.isHashed(password: string, options?: {
|
|
275
|
+
separator?: string; // Separator character (default: '$')
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Advanced usage with custom options
|
|
280
|
+
|
|
281
|
+
You can customize the hashing algorithm, salt length, iterations, encoding, and separator:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
var { ncrypt } = require('ncrypt-js');
|
|
285
|
+
|
|
286
|
+
// Generate with custom options
|
|
287
|
+
var hashedPassword = ncrypt.generate("password123", {
|
|
288
|
+
algorithm: "sha256", // Use SHA-256 instead of SHA-1
|
|
289
|
+
saltLength: 16, // Use 16 character salt
|
|
290
|
+
iterations: 1000, // Apply hashing 1000 times
|
|
291
|
+
encoding: "base64", // Use base64 encoding
|
|
292
|
+
separator: "." // Use '.' as separator instead of '$'
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Verify with matching options
|
|
296
|
+
var isValid = ncrypt.verify("password123", hashedPassword, {
|
|
297
|
+
encoding: "base64",
|
|
298
|
+
separator: "."
|
|
299
|
+
});
|
|
300
|
+
console.log(isValid); // true
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Available hash algorithms:** Any algorithm supported by Node.js `crypto.createHmac()`, such as `'sha1'`, `'sha256'`, `'sha512'`, `'md5'`, etc.
|
|
304
|
+
|
|
305
|
+
**Note:** The `encoding` and `separator` options must match between `generate()` and `verify()` calls for verification to succeed. To be safe, ensure you create a single options object and use the same object everywhere.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const same_options_obj = {
|
|
309
|
+
...
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
ncrypt.generate("pass", same_options_obj)
|
|
313
|
+
ncrypt.verify("pass", hasedPass, same_options_obj)
|
|
314
|
+
```
|
|
315
|
+
|
|
207
316
|
## Built With
|
|
208
317
|
|
|
209
318
|
Written in [TypeScript](https://typscriptlang.org/), built into ECMAScript 5 using the TypeScript compiler.
|
package/dist/src/ncrypt.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
declare type THASH_ENC = {
|
|
2
|
+
algorithm?: string;
|
|
3
|
+
saltLength?: number;
|
|
4
|
+
iterations?: number;
|
|
5
|
+
encoding?: "base64" | "hex";
|
|
6
|
+
separator?: string;
|
|
7
|
+
};
|
|
1
8
|
/**
|
|
2
9
|
* @class Ncrypt
|
|
3
10
|
* @type {Ncrypt.<object>}
|
|
@@ -20,6 +27,9 @@ export default class Ncrypt {
|
|
|
20
27
|
* crypto random initial vector generated from core node {crypto} module
|
|
21
28
|
*/
|
|
22
29
|
private readonly initialVector;
|
|
30
|
+
/**
|
|
31
|
+
* hashing salt
|
|
32
|
+
*/
|
|
23
33
|
/**
|
|
24
34
|
* crypto random key generated from core node {crypto} module
|
|
25
35
|
* {note}: please read the value for KEY from your app's environment
|
|
@@ -61,6 +71,47 @@ export default class Ncrypt {
|
|
|
61
71
|
* @returns {string.<string>} decrypted data
|
|
62
72
|
*/
|
|
63
73
|
private decode;
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* generate salt method
|
|
77
|
+
* @param length
|
|
78
|
+
* @param enc
|
|
79
|
+
* @returns
|
|
80
|
+
*/
|
|
81
|
+
private static generateSalt;
|
|
82
|
+
/**
|
|
83
|
+
*
|
|
84
|
+
* generate hash method
|
|
85
|
+
* @param algorithm
|
|
86
|
+
* @param salt
|
|
87
|
+
* @param password
|
|
88
|
+
* @param iterations
|
|
89
|
+
* @param enc
|
|
90
|
+
* @param separator
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
93
|
+
private static generateHash;
|
|
94
|
+
/**
|
|
95
|
+
*
|
|
96
|
+
* generate hashed password method
|
|
97
|
+
* @param password
|
|
98
|
+
* @param options
|
|
99
|
+
* @returns
|
|
100
|
+
*/
|
|
101
|
+
static generate(password: string, options?: THASH_ENC): string;
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
* verify password method
|
|
105
|
+
*/
|
|
106
|
+
static verify(password: string, hashedPassword: string, options?: THASH_ENC): boolean;
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* isHashed method
|
|
110
|
+
* @param password
|
|
111
|
+
* @param options
|
|
112
|
+
* @returns
|
|
113
|
+
*/
|
|
114
|
+
static isHashed(password: string, options?: THASH_ENC): boolean;
|
|
64
115
|
/**
|
|
65
116
|
* generate random strings
|
|
66
117
|
* @example
|
|
@@ -74,7 +125,7 @@ export default class Ncrypt {
|
|
|
74
125
|
* @param {enc.<string>} enc
|
|
75
126
|
* @returns {*.<string>} string
|
|
76
127
|
*/
|
|
77
|
-
static randomString(size?: number, enc?:
|
|
128
|
+
static randomString(size?: number, enc?: "hex" | "base64"): string;
|
|
78
129
|
/**
|
|
79
130
|
* data to be encrypted
|
|
80
131
|
* @param {data.<stirng>} data
|
|
@@ -88,3 +139,4 @@ export default class Ncrypt {
|
|
|
88
139
|
*/
|
|
89
140
|
decrypt(text: string): string | number | boolean | object;
|
|
90
141
|
}
|
|
142
|
+
export {};
|
package/dist/src/ncrypt.js
CHANGED
|
@@ -21,27 +21,32 @@ class Ncrypt {
|
|
|
21
21
|
/**
|
|
22
22
|
* algorithm used for encoding message
|
|
23
23
|
*/
|
|
24
|
-
this.algorithm =
|
|
24
|
+
this.algorithm = "aes-256-cbc";
|
|
25
25
|
/**
|
|
26
26
|
* ecoding for encrypted stirng
|
|
27
27
|
*/
|
|
28
|
-
this.enc =
|
|
28
|
+
this.enc = process.env.NCRYPT_ENC || "hex";
|
|
29
29
|
/**
|
|
30
30
|
* crypto random initial vector generated from core node {crypto} module
|
|
31
31
|
*/
|
|
32
32
|
this.initialVector = crypto.randomBytes(16);
|
|
33
|
+
/**
|
|
34
|
+
* hashing salt
|
|
35
|
+
*/
|
|
36
|
+
// private readonly saltChars =
|
|
37
|
+
// "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
33
38
|
/**
|
|
34
39
|
* crypto random key generated from core node {crypto} module
|
|
35
40
|
* {note}: please read the value for KEY from your app's environment
|
|
36
41
|
*/
|
|
37
|
-
this.key = crypto.scryptSync(process.env.KEY ||
|
|
42
|
+
this.key = crypto.scryptSync(process.env.KEY || "please provide a KEY in your .env file or config", "salt", 32);
|
|
38
43
|
/**
|
|
39
44
|
* convert all entered text to decimal equivalent character codes
|
|
40
45
|
* @param {text.<string>} text to be converted
|
|
41
46
|
* @return {Array.<number>} array of character codes
|
|
42
47
|
*/
|
|
43
48
|
this.convertTextToDecimal = (text) => {
|
|
44
|
-
return text.split(
|
|
49
|
+
return text.split("").map((value) => value.charCodeAt(0));
|
|
45
50
|
};
|
|
46
51
|
/**
|
|
47
52
|
* encode provided secret on decimal character codes
|
|
@@ -49,8 +54,7 @@ class Ncrypt {
|
|
|
49
54
|
* @returns {*.<number>} decimal string
|
|
50
55
|
*/
|
|
51
56
|
this.applySecretToCharacters = (charCodes) => {
|
|
52
|
-
return this.convertTextToDecimal(this.secret)
|
|
53
|
-
.reduce((firstValue, secondValue) => (firstValue ^ secondValue), charCodes);
|
|
57
|
+
return this.convertTextToDecimal(this.secret).reduce((firstValue, secondValue) => firstValue ^ secondValue, charCodes);
|
|
54
58
|
};
|
|
55
59
|
/**
|
|
56
60
|
* convert character bytes to hexadecimal equivalent
|
|
@@ -58,7 +62,7 @@ class Ncrypt {
|
|
|
58
62
|
* @returns {*.<string>} hexadecimal string
|
|
59
63
|
*/
|
|
60
64
|
this.convertByteToHexadecimal = (number) => {
|
|
61
|
-
return (
|
|
65
|
+
return ("0" + Number(number).toString(16)).substr(-2);
|
|
62
66
|
};
|
|
63
67
|
/**
|
|
64
68
|
* intermediate data encoder function
|
|
@@ -78,11 +82,11 @@ class Ncrypt {
|
|
|
78
82
|
* @returns {string.<string>} decrypted data
|
|
79
83
|
*/
|
|
80
84
|
this.decode = (text) => {
|
|
81
|
-
if (typeof text !==
|
|
82
|
-
throw new TypeError(
|
|
85
|
+
if (typeof text !== "string") {
|
|
86
|
+
throw new TypeError("argument must be a string, or a string-like object");
|
|
83
87
|
}
|
|
84
|
-
const iv = text.split(
|
|
85
|
-
const encryptedData = text.split(
|
|
88
|
+
const iv = text.split(".")[0];
|
|
89
|
+
const encryptedData = text.split(".")[1];
|
|
86
90
|
let _iv = Buffer.from(iv, this.enc);
|
|
87
91
|
let encryptedText = Buffer.from(encryptedData, this.enc);
|
|
88
92
|
let decipher = crypto.createDecipheriv(this.algorithm, Buffer.from(this.key), _iv);
|
|
@@ -95,6 +99,95 @@ class Ncrypt {
|
|
|
95
99
|
this.encrypt = this.encrypt.bind(this);
|
|
96
100
|
this.decrypt = this.decrypt.bind(this);
|
|
97
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
*
|
|
104
|
+
* generate salt method
|
|
105
|
+
* @param length
|
|
106
|
+
* @param enc
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
static generateSalt(length, enc = "hex") {
|
|
110
|
+
if (typeof length != "number" ||
|
|
111
|
+
length <= 0 ||
|
|
112
|
+
length !== parseInt(length, 10)) {
|
|
113
|
+
throw new Error("Invalid salt length");
|
|
114
|
+
}
|
|
115
|
+
// ensure your JS environment supports the `crypto.randomBytes()` function
|
|
116
|
+
return crypto
|
|
117
|
+
.randomBytes(Math.ceil(length / 2))
|
|
118
|
+
.toString(enc)
|
|
119
|
+
.substring(0, length);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
*
|
|
123
|
+
* generate hash method
|
|
124
|
+
* @param algorithm
|
|
125
|
+
* @param salt
|
|
126
|
+
* @param password
|
|
127
|
+
* @param iterations
|
|
128
|
+
* @param enc
|
|
129
|
+
* @param separator
|
|
130
|
+
* @returns
|
|
131
|
+
*/
|
|
132
|
+
static generateHash(algorithm, salt, password, iterations = 1, enc = "hex", separator = "$") {
|
|
133
|
+
try {
|
|
134
|
+
let hash = password;
|
|
135
|
+
for (let i = 0; i < iterations; ++i) {
|
|
136
|
+
hash = crypto.createHmac(algorithm, salt).update(hash).digest(enc);
|
|
137
|
+
}
|
|
138
|
+
return (algorithm + separator + salt + separator + iterations + separator + hash);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
throw new Error("Invalid message digest algorithm");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
*
|
|
146
|
+
* generate hashed password method
|
|
147
|
+
* @param password
|
|
148
|
+
* @param options
|
|
149
|
+
* @returns
|
|
150
|
+
*/
|
|
151
|
+
static generate(password, options = {}) {
|
|
152
|
+
options.algorithm = options.algorithm || "sha1";
|
|
153
|
+
options.saltLength = options.saltLength || 8;
|
|
154
|
+
options.iterations = options.iterations || 1;
|
|
155
|
+
options.encoding = options.encoding || "hex";
|
|
156
|
+
options.separator = options.separator || "$";
|
|
157
|
+
let salt = this.generateSalt(options.saltLength, options.encoding);
|
|
158
|
+
return this.generateHash(options.algorithm, salt, password, options.iterations, options.encoding, options.separator);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
* verify password method
|
|
163
|
+
*/
|
|
164
|
+
static verify(password, hashedPassword, options = {}) {
|
|
165
|
+
if (!password || !hashedPassword)
|
|
166
|
+
return false;
|
|
167
|
+
options.encoding = options.encoding || "hex";
|
|
168
|
+
options.separator = options.separator || "$";
|
|
169
|
+
let parts = hashedPassword.split(options.separator);
|
|
170
|
+
if (parts.length != 4)
|
|
171
|
+
return false;
|
|
172
|
+
try {
|
|
173
|
+
return (this.generateHash(parts[0], parts[1], password, parts[2], options.encoding, options.separator) == hashedPassword);
|
|
174
|
+
}
|
|
175
|
+
catch (e) { }
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
*
|
|
180
|
+
* isHashed method
|
|
181
|
+
* @param password
|
|
182
|
+
* @param options
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
static isHashed(password, options = {}) {
|
|
186
|
+
if (!password)
|
|
187
|
+
return false;
|
|
188
|
+
options.separator = options.separator || "$";
|
|
189
|
+
return password.split(options.separator).length == 4;
|
|
190
|
+
}
|
|
98
191
|
/**
|
|
99
192
|
* generate random strings
|
|
100
193
|
* @example
|
|
@@ -108,7 +201,7 @@ class Ncrypt {
|
|
|
108
201
|
* @param {enc.<string>} enc
|
|
109
202
|
* @returns {*.<string>} string
|
|
110
203
|
*/
|
|
111
|
-
static randomString(size, enc =
|
|
204
|
+
static randomString(size, enc = "base64") {
|
|
112
205
|
return crypto.randomBytes(size || 64).toString(enc);
|
|
113
206
|
}
|
|
114
207
|
/**
|
|
@@ -123,15 +216,17 @@ class Ncrypt {
|
|
|
123
216
|
* hexadecimal mapping
|
|
124
217
|
*/
|
|
125
218
|
try {
|
|
126
|
-
const encodedMessage = JSON.stringify(data)
|
|
219
|
+
const encodedMessage = JSON.stringify(data)
|
|
220
|
+
.split("")
|
|
127
221
|
.map(this.convertTextToDecimal)
|
|
128
222
|
.map(this.applySecretToCharacters)
|
|
129
223
|
.map(this.convertByteToHexadecimal)
|
|
130
|
-
.join(
|
|
224
|
+
.join("");
|
|
131
225
|
return this.encode(encodedMessage);
|
|
132
226
|
}
|
|
133
227
|
catch (e) {
|
|
134
|
-
throw new Error(
|
|
228
|
+
throw new Error("invalid data was entered, enter data of type object, number, string or boolean to be encrypted." +
|
|
229
|
+
e);
|
|
135
230
|
}
|
|
136
231
|
}
|
|
137
232
|
/**
|
|
@@ -141,11 +236,12 @@ class Ncrypt {
|
|
|
141
236
|
*/
|
|
142
237
|
decrypt(text) {
|
|
143
238
|
const encodeData = this.decode(text);
|
|
144
|
-
const data =
|
|
239
|
+
const data = encodeData
|
|
240
|
+
.match(/.{1,2}/g)
|
|
145
241
|
.map((hex) => parseInt(hex, 16))
|
|
146
242
|
.map(this.applySecretToCharacters)
|
|
147
243
|
.map((charCode) => String.fromCharCode(charCode))
|
|
148
|
-
.join(
|
|
244
|
+
.join("");
|
|
149
245
|
return JSON.parse(data);
|
|
150
246
|
}
|
|
151
247
|
}
|