Skip to content

Commit fe0094e

Browse files
author
kadraman
committed
Deprecated crypto API shim
1 parent d66b61e commit fe0094e

6 files changed

Lines changed: 131 additions & 34 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,6 @@ iwa.db
143143
junit.xml
144144
aviator_*
145145
.fortify
146+
147+
.otappsec
148+
fortifypackage.zip

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"fortifyRemediation.softwareSecurityCenterURL": "https://ssc.uat.fortifyhosted.net"
2+
"fortifyRemediation.softwareSecurityCenterURL": "https://ssc.stage.cyberresstage.com/"
33
}

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# InsecureRestAPI
22

3-
_InsecureRestAPI_ is a simple NodeJS/Express/MongoFB REST API that can be used for the demonstration of Application Security testing tools - such as [OpenText Application Security](https://www.opentext.com/products/application-security).
3+
_InsecureRestAPI_ is a simple NodeJS/Express/MongoDB REST API that can be used for the demonstration of Application Security testing tools - such as [OpenText Application Security](https://www.opentext.com/products/application-security).
44

55
Pre-requisities
66
---------------
@@ -18,6 +18,7 @@ You can the run the application locally using the following:
1818
```
1919
npm install
2020
npm install -g ts-node-dev
21+
npm test
2122
npm run dev
2223
```
2324

@@ -73,8 +74,8 @@ To carry out a Fortify ScanCentral SAST scan, run the following:
7374
```
7475
fcli ssc session login
7576
scancentral package -o package.zip -bt none
76-
fcli sast-scan start --release "_YOURAPP_:_YOURREL_" -f package.zip --store curScan
77-
fcli sast-scan wait-for ::curScan::
77+
fcli sc-sast scan start --publish-to "_YOURAPP_:_YOURREL_" -f package.zip --store curScan
78+
fcli sc-sast scan wait-for ::curScan::
7879
fcli ssc action run appversion-summary --av "_YOURAPP_:_YOURREL_" -fs "Security Auditor View" -f summary.md
7980
```
8081

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"build": "cross-env NODE_ENV=production npx tsc && cross-env NODE_ENV=production ts-node src/configs/swagger.config.ts && shx cp src/configs/swagger_output.json ./dist/configs",
88
"start": "cross-env NODE_ENV=production node dist/index.js",
99
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
10+
"dev:legacy": "cross-env USE_CRYPTO_BROWSERIFY=true USE_LEGACY_CIPHER=true npm run dev",
1011
"test": "cross-env jest --ci --reporters=default --reporters=jest-junit",
12+
"test:legacy": "cross-env USE_CRYPTO_BROWSERIFY=true USE_LEGACY_CIPHER=true npm run test",
1113
"swagger": "cross-env ts-node src/configs/swagger.config.ts && shx cp src/configs/swagger_output.json ./public/docs/openapi.json"
1214
},
1315
"keywords": [],

src/utils/encrypt.utils.ts

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,62 @@
1717
along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
*/
1919

20-
var crypto = require('crypto-browserify')
20+
// Allow optionally using crypto-browserify for legacy/insecure behavior during testing.
21+
let cryptoLib: any;
22+
if (process.env.USE_CRYPTO_BROWSERIFY === 'true') {
23+
// Use require so bundlers aren't forced to include this in production builds
24+
// and to allow runtime selection in dev/test scenarios.
25+
// eslint-disable-next-line @typescript-eslint/no-var-requires
26+
cryptoLib = require('crypto-browserify');
27+
} else {
28+
// eslint-disable-next-line @typescript-eslint/no-var-requires
29+
cryptoLib = require('crypto');
30+
}
2131

2232
import Logger from "../middleware/logger";
2333

34+
// Provide an optional legacy shim that emulates the deprecated OpenSSL-style
35+
// `createCipher(algorithm, password)` / `createDecipher(algorithm, password)`
36+
// by deriving a key+iv using the EVP_BytesToKey MD5 scheme. This is insecure
37+
// but useful for intentionally testing detectors that look for legacy crypto use.
38+
function evpBytesToKey(password: string, keyLen: number, ivLen: number) {
39+
const passwordBuf = Buffer.from(String(password), 'binary');
40+
let m = Buffer.alloc(0);
41+
let i = 0;
42+
let md5: Buffer = Buffer.alloc(0);
43+
while (m.length < (keyLen + ivLen)) {
44+
const hash = cryptoLib.createHash('md5');
45+
if (i === 0) {
46+
hash.update(passwordBuf);
47+
} else {
48+
hash.update(Buffer.concat([md5, passwordBuf]));
49+
}
50+
md5 = hash.digest();
51+
m = Buffer.concat([m, md5]);
52+
i++;
53+
}
54+
return {
55+
key: m.slice(0, keyLen),
56+
iv: m.slice(keyLen, keyLen + ivLen),
57+
};
58+
}
59+
60+
function createLegacyCipher(algorithm: string, password: string) {
61+
// Only implemented for AES-*-CTR/GCM/CBC families used in this project.
62+
// For `aes-256-ctr` key=32 iv=16.
63+
const keyLen = algorithm.includes('256') ? 32 : 16;
64+
const ivLen = 16;
65+
const derived = evpBytesToKey(password, keyLen, ivLen);
66+
return cryptoLib.createCipheriv(algorithm, derived.key, derived.iv);
67+
}
68+
69+
function createLegacyDecipher(algorithm: string, password: string) {
70+
const keyLen = algorithm.includes('256') ? 32 : 16;
71+
const ivLen = 16;
72+
const derived = evpBytesToKey(password, keyLen, ivLen);
73+
return cryptoLib.createDecipheriv(algorithm, derived.key, derived.iv);
74+
}
75+
2476
export abstract class EncryptUtils {
2577

2678
static jwtSecret = process.env.JWT_SECRET || "your-very-long-and-random-secret-key";
@@ -34,29 +86,59 @@ export abstract class EncryptUtils {
3486

3587
public static cryptPassword(password: String): String {
3688
//process.stdout.write("Encrypting password: " + password);
37-
var cipher = crypto.createCipher(this.algorithm, this.encryptionKey);
38-
var mystr = cipher.update(password, 'utf8', 'hex');
89+
const useLegacy = process.env.USE_LEGACY_CIPHER === 'true';
90+
if (useLegacy) {
91+
const cipher = createLegacyCipher(this.algorithm, this.encryptionKey);
92+
let mystr = cipher.update(String(password), 'utf8', 'hex');
93+
mystr += cipher.final('hex');
94+
process.stdout.write("Encrypted password:" + mystr);
95+
return mystr;
96+
}
97+
const key = cryptoLib.createHash('sha256').update(String(this.encryptionKey)).digest();
98+
const iv = cryptoLib.randomBytes(16);
99+
const cipher = cryptoLib.createCipheriv(this.algorithm, key, iv);
100+
let mystr = cipher.update(String(password), 'utf8', 'hex');
39101
mystr += cipher.final('hex');
40-
process.stdout.write("Encrypted password:" + mystr);
41-
return mystr;
102+
const payload = iv.toString('hex') + ':' + mystr;
103+
process.stdout.write("Encrypted password:" + payload);
104+
return payload;
42105
}
43106

44107
public static decryptPassword(hashPassword: String): String {
45108
process.stdout.write("Decrypting password: " + hashPassword);
46-
var cipher = crypto.createDecipher(this.algorithm, this.encryptionKey);
47-
var mystr = cipher.update(hashPassword, 'hex', 'utf8');
48-
mystr += cipher.final('utf8');
109+
const useLegacy = process.env.USE_LEGACY_CIPHER === 'true';
110+
const parts = String(hashPassword).split(':');
111+
if (parts.length !== 2) {
112+
if (useLegacy) {
113+
// Attempt legacy-style decipher (no IV prefix)
114+
try {
115+
const decipher = createLegacyDecipher(this.algorithm, this.encryptionKey);
116+
let mystr = decipher.update(String(hashPassword), 'hex', 'utf8');
117+
mystr += decipher.final('utf8');
118+
process.stdout.write("Decrypted password:" + mystr);
119+
return mystr;
120+
} catch (e) {
121+
return String(hashPassword);
122+
}
123+
}
124+
// Not in iv:encrypted format - return as-is or throw
125+
return String(hashPassword);
126+
}
127+
const iv = Buffer.from(parts[0], 'hex');
128+
const encrypted = parts[1];
129+
const key = cryptoLib.createHash('sha256').update(String(this.encryptionKey)).digest();
130+
const decipher = cryptoLib.createDecipheriv(this.algorithm, key, iv);
131+
let mystr = decipher.update(encrypted, 'hex', 'utf8');
132+
mystr += decipher.final('utf8');
49133
process.stdout.write("Decrypted password:" + mystr);
50134
return mystr;
51135
}
52136

53137
public static comparePassword(password: String, hashPassword: String): Boolean {
54138
//process.stdout.write("Encrypted password: " + hashPassword);
55-
var cipher = crypto.createDecipher(this.algorithm, this.encryptionKey);
56-
var mystr = cipher.update(hashPassword, 'hex', 'utf8');
57-
mystr += cipher.final('utf8');
58-
process.stdout.write("Comparing passwords: " + mystr + " = " + password);
59-
return (password == mystr);
139+
const plain = this.decryptPassword(hashPassword);
140+
process.stdout.write("Comparing passwords: " + plain + " = " + password);
141+
return (String(password) == String(plain));
60142
}
61143

62144
}

test/encrypt.utils.test.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
import {expect, jest, test, describe} from '@jest/globals';
2-
import { EncryptUtils } from "../src/utils/encrypt.utils"; // Adjust the path as needed
2+
import { EncryptUtils } from "../src/utils/encrypt.utils";
33

44
describe("Encryption TestCases", () => {
55

6-
test("Encrypt password", () => {
7-
//Call function of
8-
var enc = EncryptUtils.cryptPassword("ABCDE12345");
9-
// assertions
10-
expect(enc).toBe("acbf8a291bed749b6eeb");
6+
test("Encrypt/Decrypt roundtrip", () => {
7+
const plain = "ABCDE12345";
8+
const enc = EncryptUtils.cryptPassword(plain);
9+
const dec = EncryptUtils.decryptPassword(enc);
10+
expect(dec).toBe(plain);
1111

12+
// In legacy mode the ciphertext is deterministic and matches the old value
13+
if (process.env.USE_LEGACY_CIPHER === 'true' && process.env.USE_CRYPTO_BROWSERIFY === 'true') {
14+
expect(enc).toBe("acbf8a291bed749b6eeb");
15+
} else {
16+
// Modern mode: expect iv:hex format
17+
expect(typeof enc).toBe('string');
18+
expect(enc).toMatch(/^[0-9a-f]+:[0-9a-f]+$/);
19+
}
1220
});
1321

14-
test("Decrypt password", () => {
15-
//Call function of
16-
var enc = EncryptUtils.decryptPassword("acbf8a291bed749b6eeb");
17-
// assertions
18-
expect(enc).toBe("ABCDE12345");
22+
test("Decrypt legacy literal", () => {
23+
if (process.env.USE_LEGACY_CIPHER === 'true' && process.env.USE_CRYPTO_BROWSERIFY === 'true') {
24+
const dec = EncryptUtils.decryptPassword("acbf8a291bed749b6eeb");
25+
expect(dec).toBe("ABCDE12345");
26+
} else {
27+
// In modern mode, attempting to decrypt a legacy literal returns it unchanged
28+
const dec = EncryptUtils.decryptPassword("acbf8a291bed749b6eeb");
29+
expect(dec).toBe("acbf8a291bed749b6eeb");
30+
}
1931
});
2032

21-
2233
test("Compare passwords", () => {
23-
//Call function of
24-
var enc = EncryptUtils.cryptPassword("ABCDE12345");
25-
// assertions
26-
expect(EncryptUtils.comparePassword("ABCDE12345", enc)).toBeTruthy;
27-
expect(EncryptUtils.comparePassword("12345ABCDE", enc)).toBeFalsy;
34+
const enc = EncryptUtils.cryptPassword("ABCDE12345");
35+
expect(EncryptUtils.comparePassword("ABCDE12345", enc)).toBeTruthy();
36+
expect(EncryptUtils.comparePassword("12345ABCDE", enc)).toBeFalsy();
2837
});
2938

3039
});

0 commit comments

Comments
 (0)