├── go-demo ├── go.mod └── main.go ├── nodejs-proxy ├── .prettierrc.json ├── utils │ ├── commonUtil.js │ ├── levelDB.js │ ├── flowEnc.js │ └── httpClient.js ├── webdavClient.js ├── .eslintrc.json ├── config.js ├── netproxy.js ├── package.json ├── httpProxy.js └── app.js ├── .gitignore ├── README.md └── java-demo └── src └── com └── example └── test ├── RC4Demo.java └── FlowEnc.java /go-demo/go.mod: -------------------------------------------------------------------------------- 1 | module example.com/test 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /nodejs-proxy/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "printWidth": 150, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /nodejs-proxy/utils/commonUtil.js: -------------------------------------------------------------------------------- 1 | import { pathToRegexp } from 'path-to-regexp' 2 | 3 | // 判断是否为匹配的路径 4 | export function pathExec(encPath, url) { 5 | for (const path of encPath) { 6 | const result = pathToRegexp(new RegExp(path)).exec(url) 7 | if (result) { 8 | return result 9 | } 10 | } 11 | return null 12 | } 13 | -------------------------------------------------------------------------------- /nodejs-proxy/webdavClient.js: -------------------------------------------------------------------------------- 1 | import { createClient } from 'webdav' 2 | 3 | const url = 'http://192.168.8.21:5344/dav/tianyi' 4 | const client = createClient(url, { 5 | username: 'admin', 6 | password: 'YiuNH7ly' 7 | }) 8 | 9 | const start = async function () { 10 | const directoryItems = await client.getDirectoryContents('/') 11 | console.log('directoryItems', directoryItems) 12 | } 13 | 14 | start() 15 | -------------------------------------------------------------------------------- /nodejs-proxy/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true, 6 | "es2021": true 7 | }, 8 | // airbnb-base, "plugin:prettier/recommended" 9 | "extends": ["standard"], 10 | "overrides": [], 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "space-before-function-paren": 0, 17 | "space-in-parens": "off", 18 | "comma-dangle": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.class 4 | *.jar 5 | *.war 6 | *.zip 7 | *.tar 8 | *.tar.gz 9 | dependency-reduced-pom.xml 10 | 11 | # maven plugin ignore 12 | release.properties 13 | cobertura.ser 14 | *.gpg 15 | 16 | # eclipse ignore 17 | .settings/ 18 | node_modules/ 19 | db-data/ 20 | bin 21 | .vscode 22 | .project 23 | .classpath 24 | 25 | # idea ignore 33 26 | .idea/ 27 | *.ipr 28 | *.iml 29 | *.iws 30 | 31 | # temp ignore 32 | logs/ 33 | *.log 34 | *.doc 35 | *.cache 36 | *.diff 37 | *.patch 38 | *.tmp 39 | 40 | # system ignore 额 41 | .DS_Store 42 | Thumbs.db 43 | 44 | # agent build ignore 45 | /agent/ 46 | 47 | -------------------------------------------------------------------------------- /nodejs-proxy/config.js: -------------------------------------------------------------------------------- 1 | // 会当做Md5的salt,当前预留配置 2 | export const userPasswd = '123456' 3 | 4 | // 全局代理alist,包括它的webdav和http服务,要配置上 5 | export const alistServer = { 6 | path: '/*', // 默认就是代理全部,不建议修改这里 7 | serverHost: '192.168.8.240', 8 | serverPort: 5244, 9 | flowPassword: '123456', // 加密的密码 10 | encPath: ['/aliy/test/*', '/aliy/test/*', '/tianyi/*'], // 注意不需要添加/dav 前缀了,程序会自己处理alist的逻辑,支持js正则,不能是 "/*" 和 "/proxy/*",因为已经占用 11 | } 12 | 13 | // 支持其他普通的webdav(当然也可以挂载alist的webdav) 14 | export const webdavServer = [ 15 | { 16 | name: 'aliyun', 17 | path: '/dav/*', // 代理全部路径,不能是"/proxy/*",系统已占用。如果设置 "/*",那么上面的alist的配置就不会生效哦 18 | enable: false, // 是否启动代理 19 | serverHost: '192.168.8.234', 20 | serverPort: 5244, 21 | flowPassword: '123456', // 加密的密码 22 | encPath: ['/dav/aliyun/*', '/dav/189cloud/*'], // 要加密的目录,支持js正则,不能是 "/*" 和 "/proxy/*",因为已经占用 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /nodejs-proxy/netproxy.js: -------------------------------------------------------------------------------- 1 | import net from 'net' 2 | 3 | const server = net.createServer() 4 | // 监听connect事件 5 | server.on('connection', (proxySocket) => { 6 | const serverSocket = net.connect( 7 | { port: 5244, host: '192.168.8.240' }, 8 | () => { 9 | console.log('连接到服务器') 10 | } 11 | ) 12 | let clientRequest = '' 13 | let serverResp = '' 14 | proxySocket.on('data', (data) => { 15 | clientRequest += data 16 | serverSocket.write(data) 17 | console.log('@@clientRequest:\r\n', clientRequest) 18 | }) 19 | serverSocket.on('data', (data) => { 20 | serverResp += data 21 | proxySocket.write(data) 22 | console.log('@@serverResp:\r\n', serverResp) 23 | }) 24 | // serverSocket.pipe(proxySocket) 25 | // proxySocket.pipe(serverSocket) 26 | }) 27 | // 设置监听端口 28 | server.listen(5244) 29 | 30 | // 设置监听时的回调函数 31 | server.on('listening', (res) => { 32 | console.log('server in listen...', 5244) 33 | }) 34 | -------------------------------------------------------------------------------- /nodejs-proxy/utils/levelDB.js: -------------------------------------------------------------------------------- 1 | import { Level } from 'level' 2 | import path from 'path' 3 | 4 | class LevelDB extends Level { 5 | // 新增过期设置 6 | async putValue(key, value, second = 60 * 10) { 7 | const expire = Date.now() + second * 1000 8 | const data = { expire, value } 9 | return await this.put(key, data) 10 | } 11 | 12 | async getValue(key) { 13 | try { 14 | const { expire, value } = await this.get(key) 15 | if (expire && expire > Date.now()) { 16 | return value 17 | } 18 | } catch (e) { 19 | return null 20 | } 21 | // 删除key 22 | levelDB.del(key) 23 | return null 24 | } 25 | } 26 | const levelDB = new LevelDB(path.resolve('db-data'), { valueEncoding: 'json' }) 27 | // 定时清除过期的数据 28 | setInterval(async () => { 29 | for await (const [key, data] of levelDB.iterator()) { 30 | const { expire } = data 31 | if (expire && expire < Date.now()) { 32 | console.log('@@expire:', key, expire, Date.now()) 33 | levelDB.del(key) 34 | } 35 | } 36 | }, 30 * 1000) 37 | export default levelDB 38 | -------------------------------------------------------------------------------- /nodejs-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cache": "^3.0.0", 15 | "ejs": "^3.1.8", 16 | "koa": "^2.13.1", 17 | "koa-body": "^5.0.0", 18 | "koa-bodyparser": "^4.3.0", 19 | "koa-router": "^10.0.0", 20 | "path-to-regexp": "^6.1.0", 21 | "koa-views": "^8.0.0", 22 | "level": "^8.0.0", 23 | "log4js": "^6.9.1", 24 | "sqlite3": "^5.1.6", 25 | "webdav": "^5.0.0-r3", 26 | "webdav-server": "^2.6.2", 27 | "websocket": "^1.0.33" 28 | }, 29 | "devDependencies": { 30 | "@typescript-eslint/eslint-plugin": "^5.55.0", 31 | "@typescript-eslint/parser": "^5.55.0", 32 | "eslint": "^8.36.0", 33 | "eslint-config-standard": "^17.0.0", 34 | "eslint-plugin-import": "^2.27.5", 35 | "eslint-plugin-n": "^15.6.1", 36 | "eslint-plugin-promise": "^6.1.1", 37 | "prettier": "^2.8.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /go-demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "fmt" 8 | "io" 9 | ) 10 | 11 | // MD5加密 12 | func genMd5(code string) (string, [16]byte) { 13 | c1 := md5.Sum([]byte(code)) //返回[16]byte数组 14 | Md5 := md5.New() 15 | _, _ = io.WriteString(Md5, code) 16 | return hex.EncodeToString(Md5.Sum(nil)), c1 17 | } 18 | 19 | // 初始化code和decode 的数据 20 | func initEnc(password string) ([16]byte, [16]byte) { 21 | // 初始化[16]byte数组 22 | var md5Str, encode = genMd5(password) 23 | var decode [16]byte 24 | fmt.Println("md5Str: ", md5Str) 25 | 26 | // 仅仅用来判断是否已经存在index 27 | decodeCheck := make(map[int]byte, 16) 28 | 29 | var i, j int 30 | for i = 0; i < len(encode); i++ { 31 | var intd = uint8(encode[i]) 32 | var dec = int(intd) ^ i 33 | _, exist := decodeCheck[dec%16] 34 | if !exist { 35 | decodeCheck[dec%16] = encode[i] 36 | decode[dec%16] = encode[i] 37 | fmt.Println("取模 ", dec%16) 38 | } else { 39 | for j = 0; j < 16; j++ { 40 | _, exist := decodeCheck[j] 41 | if !exist { 42 | // 兜底,把 encode[i]后四位转成 j ^ i 的二进制值,确保decode的后四位不冲突 43 | encode[i] = encode[i]&0xF0 | byte((j ^ i)) 44 | decode[j] = encode[i] 45 | decodeCheck[j] = decode[j] 46 | fmt.Println("#取模 ", j) 47 | break 48 | } 49 | } 50 | } 51 | } 52 | fmt.Println("encode: ", encode) 53 | fmt.Println("decode: ", decode) 54 | return encode, decode 55 | } 56 | 57 | func encodeData(data []byte, encode [16]byte) []byte { 58 | var array []byte = make([]byte, len(data)) 59 | var i int 60 | for i = 0; i < len(data); i++ { 61 | var index = uint8(data[i]) % 16 62 | var enc = encode[index] ^ data[i] 63 | var encdata = enc & 0xFF 64 | array[i] = encdata 65 | } 66 | return array 67 | } 68 | 69 | func decodeData(data []byte, decode [16]byte) []byte { 70 | var array []byte = make([]byte, len(data)) 71 | var i int 72 | for i = 0; i < len(data); i++ { 73 | var index = uint8(data[i]) % 16 74 | var inx = decode[index] ^ data[i] 75 | var encdata = inx & 0xFF 76 | array[i] = encdata 77 | } 78 | return array 79 | } 80 | 81 | func main() { 82 | var encode, decode = initEnc("abc1234") 83 | var plaintext = "测试的明文加密1234¥%#" 84 | var encodeBytes = encodeData([]byte(plaintext), encode) 85 | var base64Str = base64.StdEncoding.EncodeToString(encodeBytes) 86 | // 加密后输入base64 87 | fmt.Println("base64Str:", base64Str) 88 | var decodeBytes = decodeData(encodeBytes, decode) 89 | // 输出明文 90 | plaintext = string(decodeBytes) 91 | fmt.Println("解密后plaintext:", plaintext) 92 | 93 | } 94 | -------------------------------------------------------------------------------- /nodejs-proxy/utils/flowEnc.js: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import { Transform } from 'stream' 3 | import { userPasswd } from '../config.js' 4 | 5 | class FlowEnc { 6 | constructor(password) { 7 | const md5 = crypto.createHash('md5') 8 | const encode = md5.update(password).digest() 9 | const decode = [] 10 | const length = encode.length 11 | const decodeCheck = {} 12 | for (let i = 0; i < length; i++) { 13 | const enc = encode[i] ^ i 14 | // 这里会产生冲突 15 | if (!decodeCheck[enc % length]) { 16 | // console.log("取模 " + enc % length) 17 | decode[enc % length] = encode[i] & 0xff 18 | decodeCheck[enc % length] = encode[i] 19 | } else { 20 | for (let j = 0; j < length; j++) { 21 | if (!decodeCheck[j]) { 22 | // 兜底,把 encode[i]后四位转成 j ^ i 的二进制值,确保decode的后四位不冲突 23 | encode[i] = (encode[i] & 0xf0) | (j ^ i) 24 | decode[j] = encode[i] & 0xff 25 | decodeCheck[j] = encode[i] 26 | // console.log("#取模 " + j) 27 | break 28 | } 29 | } 30 | } 31 | } 32 | this.password = password 33 | this.passwordMd5 = crypto.createHash('md5').digest('hex') 34 | this.encode = encode 35 | this.decode = Buffer.from(decode) 36 | // MD5 37 | this.md5 = function (content) { 38 | const md5 = crypto.createHash('md5') 39 | return md5.update(this.passwordMd5 + content).digest('hex') 40 | } 41 | // 加密流转换 42 | this.encodeTransform = function () { 43 | return new Transform({ 44 | // 匿名函数确保this是指向 FlowEnc 45 | transform: (chunk, encoding, next) => { 46 | next(null, this.encodeData(chunk)) 47 | }, 48 | }) 49 | } 50 | // 解密流转换,不能单实例 51 | this.decodeTransform = function () { 52 | return new Transform({ 53 | transform: (chunk, encoding, next) => { 54 | // this.push() 用push也可以 55 | next(null, this.decodeData(chunk)) 56 | }, 57 | }) 58 | } 59 | // 不处理 60 | this.testTransform = function () { 61 | return new Transform({ 62 | transform: function (chunk, encoding, callback) { 63 | callback(null, chunk) 64 | }, 65 | }) 66 | } 67 | } 68 | 69 | // 加密方法 70 | encodeData(data) { 71 | data = Buffer.from(data) 72 | for (let i = data.length; i--; ) { 73 | data[i] = this.encode[data[i] % 16] ^ (data[i] & 0xff) 74 | } 75 | return data 76 | } 77 | 78 | // 解密方法 79 | decodeData(data) { 80 | for (let i = data.length; i--; ) { 81 | data[i] = this.decode[data[i] % 16] ^ (data[i] & 0xff) 82 | } 83 | return data 84 | } 85 | } 86 | FlowEnc.encMd5 = function (content) { 87 | const md5 = crypto.createHash('md5') 88 | return md5.update(userPasswd + content).digest('hex') 89 | } 90 | // const flowEnc = new FlowEnc('abc1234') 91 | // const encode = flowEnc.encodeData('测试的明文加密1234¥%#') 92 | // const decode = flowEnc.decodeData(encode) 93 | // console.log('@@@decode', encode, decode.toString()) 94 | export default FlowEnc 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TLF-encryption 2 | 3 | flow-encryption 一个简单的流加密算法,可以用在对加密要求不高的地方,有一些场景还是非常适合的。R4C 的算法方案固然是更好的,但是实现和使用起来会比较复杂一些,对业务代码的耦合比较强。这个加密实现简单,可以直接代理中间的流量。 4 | 5 | 这个算法的应用已经迁移到迁仓库,https://github.com/traceless/alist-encrypt RC4算法目前也加入到此项应用中,也是支持RC4方式加密,安全性更好。播放跳转的时候会慢一些(后续会优化掉),但不影响正常使用。 6 | 7 | ## 一、实现的思路 8 | 9 | 1、关于流加密的算法有很多,基本原理都是对字节进行`异或运算`,最简单的一个流加密实现就是一个固定的字节,比如 11001100 和每个字节进行异或运算,但是这样的方式就很容易被别人破解。 10 | 11 | 2、那么我们可以简单的准备一组加密的字节,比如 11001100,10001000,10011001...,如果明文的字节为 10001000,那么它对应的 encode 加密字节就为 11001100,以此类推,所以我们要准备一个明文和加密 byte 的`映射`。 12 | 13 | 3、假设我们随机生成 16 个加密的字节(暂用 int 整型来标识) encode: [161 65 196 121 39 146 155 194 209 251 110 51 103 33 96 240] ,明文的后四位(0-15)就是对应 ecode 的数组的`index位置`。比如明文 xxxx0001,那么它对应就是 ecode[1],如 xxxx0011 -> encode[3],以此类推,所有的明文都可以找到对应的 ecode byte。 14 | 15 | 我们可以得到明文和加密 encode 数组的映射: 16 | 17 | | 明文 byte | xxxx0000 | xxxx0001 | xxxx0010 | xxxx0011 | xxxx0100 | ... | 18 | | :---------- | -------: | -------: | -------: | -------: | -------: | --: | 19 | | 加密 byte | 10100001 | 01000001 | 11000100 | 01111001 | 00100111 | ... | 20 | | 加密后 byte | xxxx0001 | xxxx0000 | xxxx0110 | xxxx1010 | xxxx0011 | ... | 21 | | 密文后四位 | 1 | 0 | 6 | 10 | 3 | ... | 22 | 23 | 从上面可以得出加密后的密文的规则,比如密文等于 xxxx0001,那么它对应的 encode 加密字节就是 10100001,如:xxxx0011 -> 00100111,按照这方式进行解密即可。但是密文后四位会有冲突,很可能都会出现 xxxx0010 ^ 11000100(encode) = 6,xxxx0100 ^ 11110010(encode) = 6 ,只要找一个没有被占用的数字比如 2,然后修改 ecode byte`使得密文后四位`等于 1111 0110 就可以了。 24 | 25 | ecode 的数组生成我们可以使用 MD5 的方式进行创建,然后再生成 decode 的数组,当然解决取模冲突后,原本 encode 也会发生变化,具体看代码实现。 26 | 27 | ## 二、算法的优缺点 28 | 29 | - 缺点:这个算法实现比较简单,破解的难度我也不好去验证,毕竟是固定的密码,通过某些流数据特征还是比较容易破解的(比如 class 文件开头的四个字节是固定的 0x CAFEBABE)。如果是单纯的暴力破解目前还是没什么机会的。另外可以根据原文件就能得到 encode 和 decode,这个解决的方案在算法优化有提到。 30 | 31 | - 优点:算法很简单,可以嵌入到很多代理的中间件中,对流的加密不用入侵到业务代码中,可透传加解密,没有中间停留的过程。另外文件解密只要给对方 encode\decode 就可以了。 32 | 33 | ## 三、算法的优化方向 34 | 35 | 1、因为上面的是固定加密 encode,如果 encode 泄露了,那么其他的已经加密的数据也会出现问题。那么我们也可以通过流的某些属性或特征作为 MD5 的 salt,这样每次生成的密码本也会不一样。比如传输的是文件,那么文件流的长度是比较容易获取到的,可以作为 md5 的 salt。 36 | 37 | 2、目前采用的是 16 个字节,如果要换成 32 个字节,那么可能破解的难度是否增加?这个我没有去研究,毕竟 16 个字节破解的难度已经很大了,粗算大于 1.8x10^19 \* 10^N(N不知道有多大,保守大于8)。 38 | 39 | ## 四、其他流算法 40 | 41 | 1、流密码算法有 RC4、A5/1、ZUC 等,它们的实现可能会更加安全,也可以用业内一些成熟的方案。不过还是要根据实际业务需求来使用,这些算法也许并不能满足业务需求。 42 | 43 | ## 五、应用场景 44 | 45 | ### 1、http 代理思路 46 | 47 | 因为考虑到 webdav 是基于 http 协议的,那么理论上完成 http 代理的实现就包含了 webdav 的实现了。由于只拦截 body 进行加解密,所以请求头 headers 和响应头 headers 就不需要动了,基本就是原路透传。 48 | 49 | 实现的思路: 50 | 51 | 1. 解析请求头 headers,然后传到 webdav 服务器中,请求的 body 按业务进行是否加密。 52 | 2. 解析响应头 headers,并原路返回到客户端中,响应的 body 按业务判断进行是否解密。 53 | 54 | > 其他语言就可以根据这样的思路进行分析和拦截,node.js 提供的 http 模块非常的方便实现这样的业务。 55 | 56 | ### 2、http 代理的实现 57 | 58 | 1、httpProxy.js 是一个 http 代理服务器的最基础的实现,里面有对 http 的 body 流进行拦截加解密,可用于学习和参考。不得称赞一下 node.js 所提供 http 模块的实现,直接提供了 body 的读写流,还有 Transform 转换流接口,开发起来扛扛的。换其他语言,估计折腾大半天。 59 | 60 | 2、app.js 是一个 http 代理服务器的基本实现,可以针对性对 webdav 的流量进行挟持,使用流加密的方式可以对上传的文件,下载的文件进行加解密。也可以对一些在线视频播放的流进行实时解密。目前只代理了/dav/\*的路径用于测试和验证,也可以改成代理整个 alist。 61 | 62 | 3、目前给出的 ndoejs 的 demo 版本,测试阿里云盘的上传,删除,移动,下载(302 已解决)都可以正常使用,也能正常加密和解密。配置文件参考下 config.js。 63 | 64 | ## 六、后续算法应用 65 | 66 | 这个项目的应用已经迁移到迁仓库,https://github.com/traceless/alist-encrypt 67 | -------------------------------------------------------------------------------- /java-demo/src/com/example/test/RC4Demo.java: -------------------------------------------------------------------------------- 1 | package com.example.test; 2 | 3 | import java.util.Random; 4 | import java.util.Scanner; 5 | 6 | public class RC4Demo { 7 | public static void main(String[] args) { 8 | // 输入明文 9 | System.out.println("请输入明文:"); 10 | Scanner sc = new Scanner(System.in); 11 | String plainText = sc.nextLine(); 12 | 13 | // 获取随机密钥 14 | System.out.println("请输入密钥长度:"); 15 | int keylen = sc.nextInt(); 16 | String key = getKey(keylen); 17 | System.out.println("随机密钥是:" + key); 18 | 19 | // 进行加密和解密并打印 20 | String cipher = Encrypt(plainText, key); 21 | String plainer = Encrypt(cipher, key); 22 | System.out.println("'" + plainText + "'" + "加密后密文是:" + cipher); 23 | System.out.println("'" + cipher + "'" + "解密后明文是:" + plainer); 24 | } 25 | 26 | // 随机密钥方法 27 | public static String getKey(int keylen) { 28 | char[] k = new char[keylen]; 29 | Random r = new Random(); 30 | for (int i = 0; i < keylen; i++) { 31 | k[i] = (char) ('a' + r.nextInt() % 26); 32 | } 33 | String s = new String(k); 34 | return s; 35 | } 36 | 37 | // 加密方法 38 | public static String Encrypt(String plainText, String key) { 39 | // 1.1 初始化一个S表和一个K表 40 | int[] S = new int[256]; 41 | byte[] K = new byte[256]; 42 | 43 | // 初始化一个密钥流 44 | Character[] keySchedul = new Character[plainText.length()]; 45 | 46 | // 1.2 调用KSA方法对S表进行初始排列 47 | KSA(S, K, key); 48 | 49 | // 1.3 调用PRGA方法产生密钥流 50 | PRGA(S, keySchedul, plainText.length()); 51 | 52 | // 存放密文结果 53 | StringBuffer cipherResult = new StringBuffer(); 54 | 55 | // 进行加密--用明文和密钥流进行异或操作 56 | for (int i = 0; i < plainText.length(); i++) { 57 | int num = plainText.charAt(i) ^ keySchedul[i]; 58 | System.out.println("@@@:" + num); 59 | cipherResult.append((char) (plainText.charAt(i) ^ keySchedul[i])); 60 | } 61 | 62 | return cipherResult.toString(); 63 | } 64 | 65 | // RSA密钥调度算法---实现用K表对S表的置换 66 | public static void KSA(int[] S, byte[] K, String keyStr) { 67 | byte[] key = keyStr.getBytes(); 68 | // 对S表进行初始赋值 69 | for (int i = 0; i < 256; i++) { 70 | S[i] = i; 71 | } 72 | // 用种子密钥对K表进行填充 73 | for (int i = 0; i < 256; i++) { 74 | K[i] = (byte) key[i % key.length]; 75 | 76 | } 77 | // 对S表进行置换 78 | int j = 0; 79 | for (int i = 0; i < 256; i++) { 80 | j = (j + S[i] + K[i]) % 256; 81 | swap(S, i, j); 82 | } 83 | StringBuffer kbuffer = new StringBuffer(); 84 | for (int i = 0; i < 256; i++) { 85 | kbuffer.append(S[i] + ","); 86 | } 87 | 88 | System.out.println(kbuffer.toString()); 89 | } 90 | 91 | // PRGA--伪随机生成算法--利用上面重新排列的S盒来产生任意长度的密钥流 92 | public static void PRGA(int[] S, Character[] keySchedul, int plainTextLen) { 93 | int i = 0, j = 0; 94 | for (int k = 0; k < plainTextLen; k++) { 95 | i = (i + 1) % 256; 96 | j = (j + S[i]) % 256; 97 | swap(S, i, j); 98 | keySchedul[k] = (char) (S[(S[i] + S[j]) % 256]); 99 | } 100 | 101 | 102 | } 103 | 104 | // 交换方法,实现交换两个位置的元素 105 | public static void swap(int[] S, int i, int j) { 106 | int temp = S[i]; 107 | S[i] = S[j]; 108 | S[j] = temp; 109 | } 110 | } -------------------------------------------------------------------------------- /nodejs-proxy/httpProxy.js: -------------------------------------------------------------------------------- 1 | import http from 'http' 2 | import FlowEnc from './utils/flowEnc.js' 3 | import https from 'node:https' 4 | const Agent = http.Agent 5 | const Agents = https.Agent 6 | 7 | const flowEnc = new FlowEnc('123456') 8 | // 连接数无所谓多少了 9 | const maxSockets = 50 10 | const httpsAgent = new Agents({ 11 | maxSockets, 12 | maxFreeSockets: maxSockets, 13 | keepAlive: true 14 | }) 15 | const httpAgent = new Agent({ 16 | maxSockets, 17 | maxFreeSockets: maxSockets, 18 | keepAlive: true 19 | }) 20 | 21 | const webdavServerHost = '192.168.8.240' 22 | const webdavServerPort = 5244 23 | let authorization = null 24 | 25 | // http客户端请求 26 | function httpClient(request, response, redirect = 3) { 27 | const { method, headers, urlAddr } = request 28 | console.log('request_info: ', headers, method, urlAddr) 29 | // 创建请求 30 | const options = { 31 | method, 32 | headers, 33 | agent: ~urlAddr.indexOf('https') ? httpsAgent : httpAgent, 34 | rejectUnauthorized: false 35 | } 36 | const httpRequest = ~urlAddr.indexOf('https') ? https : http 37 | // 处理重定向的请求,让下载的流量经过代理服务器 38 | const webdavReq = httpRequest.request(urlAddr, options, (webdavResp) => { 39 | console.log('@@statusCode', webdavResp.statusCode, webdavResp.headers) 40 | response.statusCode = webdavResp.statusCode 41 | for (const key in webdavResp.headers) { 42 | response.setHeader(key, webdavResp.headers[key]) 43 | } 44 | if (response.statusCode === 302 || response.statusCode === 301) { 45 | if (redirect <= 0) { 46 | // 防止无限重定向, 结束重定向 47 | console.log('httpResp结束重定向...') 48 | response.end() 49 | return 50 | } 51 | // 重新请求一次,把流量代理进来 52 | request.urlAddr = webdavResp.headers.location 53 | delete request.headers.host 54 | delete request.headers.authorization 55 | request.method = 'GET' 56 | console.log('302 redirect: ', request.urlAddr) 57 | return httpClient(request, response, redirect - 1).end() 58 | } 59 | let resLength = 0 60 | webdavResp 61 | .on('data', (chunk) => { 62 | resLength += chunk.length 63 | }) 64 | .on('end', () => { 65 | console.log('httpResp响应结束...', resLength, request.url) 66 | }) 67 | // 如果要解密那么就直接pipe,这里需要判断路径,还有识别当前是下载请求 TODO 68 | if (~urlAddr.indexOf('https')) { 69 | // 可以在这里添加条件判断进行加解密 TODO 70 | webdavResp.pipe(flowEnc.decodeTransform()).pipe(response) 71 | return 72 | } 73 | // 直接透传 74 | webdavResp.pipe(response) 75 | }) 76 | return webdavReq 77 | } 78 | 79 | // 创建一个 HTTP 代理服务器 80 | const proxy = http.createServer((req, res) => {}) 81 | // 监听请求,实时处理透传数据到下游webdav 82 | proxy.on('request', (request, response) => { 83 | // 缓存起来,很多客户端并不是每次都会携带 authorization,导致上传文件一些异常,不想catch了,直接每次携带 authorization 84 | request.headers.authorization = request.headers.authorization 85 | ? (authorization = request.headers.authorization) 86 | : authorization 87 | // headers 所有都透传,不删除,host需要单独修改,实测没影响 88 | request.headers.host = webdavServerHost + ':' + webdavServerPort 89 | request.urlAddr = `http://${request.headers.host}` + request.url 90 | // 如果是上传文件,那么进行流加密 91 | if (request.method.toLocaleUpperCase() === 'PUT') { 92 | const webdavReq = httpClient(request, response) 93 | // 这里就进行文件上传,可以进行加解密,一般判断路径 TODO 94 | request.pipe(flowEnc.encodeTransform()).pipe(webdavReq) 95 | return 96 | } 97 | const webdavReq = httpClient(request, response) 98 | request.pipe(webdavReq) 99 | }) 100 | 101 | // 代理服务器正在运行 102 | const port = 5344 103 | proxy.listen(port, () => { 104 | console.log(' webdav proxy start ', port) 105 | }) 106 | -------------------------------------------------------------------------------- /nodejs-proxy/utils/httpClient.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-dangle */ 2 | import http from 'http' 3 | import https from 'node:https' 4 | import crypto from 'crypto' 5 | import levelDB from './levelDB.js' 6 | import { pathExec } from './commonUtil.js' 7 | const Agent = http.Agent 8 | const Agents = https.Agent 9 | 10 | // 默认maxFreeSockets=256 11 | const httpsAgent = new Agents({ keepAlive: true }) 12 | const httpAgent = new Agent({ keepAlive: true }) 13 | 14 | export async function httpProxy(request, response, encodeTransform, decodeTransform) { 15 | const { method, headers, urlAddr, webdavConfig } = request 16 | console.log('request_info: ', method, urlAddr, headers) 17 | // 创建请求 18 | const options = { 19 | method, 20 | headers, 21 | agent: ~urlAddr.indexOf('https') ? httpsAgent : httpAgent, 22 | rejectUnauthorized: false, 23 | } 24 | const httpRequest = ~urlAddr.indexOf('https') ? https : http 25 | return new Promise((resolve, reject) => { 26 | // 处理重定向的请求,让下载的流量经过代理服务器 27 | const httpReq = httpRequest.request(urlAddr, options, async (httpResp) => { 28 | console.log('@@statusCode', httpResp.statusCode, httpResp.headers) 29 | response.statusCode = httpResp.statusCode 30 | if (response.statusCode % 300 < 5) { 31 | // 可能出现304,redirectUrl = undefined 32 | const redirectUrl = httpResp.headers.location || '-' 33 | // 跳转到本地服务进行重定向下载 ,简单判断是否https那说明是请求云盘资源,后续完善其他业务判断条件 TODO 34 | const decode = ~redirectUrl.indexOf('https') 35 | // 因为天翼云会多次302,所以这里要保持,跳转后的路径保持跟上次一致,经过本服务器代理就可以解密 36 | if (decode && webdavConfig && pathExec(webdavConfig.encPath, request.url)) { 37 | const key = crypto.randomUUID() 38 | await levelDB.putValue(key, { redirectUrl, webdavConfig }, 60 * 60 * 72) // 缓存起来,默认3天,足够下载和观看了 39 | httpResp.headers.location = `/redirect/${key}?decode=${decode}&lastUrl=${encodeURIComponent(request.url)}` 40 | } 41 | console.log('302 redirectUrl:', redirectUrl) 42 | } 43 | // 设置headers 44 | for (const key in httpResp.headers) { 45 | response.setHeader(key, httpResp.headers[key]) 46 | } 47 | let resLength = 0 48 | httpResp 49 | .on('data', (chunk) => { 50 | resLength += chunk.length 51 | }) 52 | .on('end', () => { 53 | resolve(resLength) 54 | console.log('httpResp响应结束...', resLength, request.url) 55 | }) 56 | // 是否需要解密 57 | decodeTransform ? httpResp.pipe(decodeTransform).pipe(response) : httpResp.pipe(response) 58 | }) 59 | // 是否需要加密 60 | encodeTransform ? request.pipe(encodeTransform).pipe(httpReq) : request.pipe(httpReq) 61 | }) 62 | } 63 | 64 | export async function httpClient(request, response, encodeTransform, decodeTransform) { 65 | const { method, headers, urlAddr, reqBody } = request 66 | console.log('request_info: ', method, urlAddr, headers) 67 | // 创建请求 68 | const options = { 69 | method, 70 | headers, 71 | agent: ~urlAddr.indexOf('https') ? httpsAgent : httpAgent, 72 | rejectUnauthorized: false, 73 | } 74 | const httpRequest = ~urlAddr.indexOf('https') ? https : http 75 | return new Promise((resolve, reject) => { 76 | // 处理重定向的请求,让下载的流量经过代理服务器 77 | const httpReq = httpRequest.request(urlAddr, options, async (httpResp) => { 78 | console.log('@@statusCode', httpResp.statusCode, httpResp.headers) 79 | response.statusCode = httpResp.statusCode 80 | // 设置headers 81 | for (const key in httpResp.headers) { 82 | response.setHeader(key, httpResp.headers[key]) 83 | } 84 | let result = '' 85 | httpResp 86 | .on('data', (chunk) => { 87 | result += chunk 88 | }) 89 | .on('end', () => { 90 | resolve(result) 91 | console.log('httpResp响应结束...', result, request.url) 92 | }) 93 | }) 94 | // 是否需要加密 95 | httpReq.write(reqBody) 96 | httpReq.end() 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /java-demo/src/com/example/test/FlowEnc.java: -------------------------------------------------------------------------------- 1 | package com.example.test; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | import java.util.Base64; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | 11 | /** 12 | * @author doctor 13 | * @date 2021-06-28 14 | */ 15 | public class FlowEnc { 16 | 17 | public static byte[] encode; 18 | 19 | public static byte[] decode = new byte[16]; 20 | 21 | public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException { 22 | 23 | String paasword = "abc1234"; 24 | String plaintext = "测试的明文加密1234¥%#"; 25 | initEnc(paasword); 26 | 27 | String base64Str = encodeData(plaintext); 28 | System.out.println("base64Str: " + base64Str); 29 | plaintext = decodeData(base64Str); 30 | System.out.println("解密plaintext: " + plaintext); 31 | 32 | } 33 | 34 | public static void initEnc(String password) throws NoSuchAlgorithmException { 35 | MessageDigest md = MessageDigest.getInstance("MD5"); 36 | encode = md.digest(password.getBytes()); 37 | int length = encode.length; 38 | Set decodeCheck = new HashSet<>(); 39 | for (int i = 0; i < length; i++) { 40 | int enc = Byte.toUnsignedInt(encode[i]) ^ i; 41 | // 这里会产生冲突 42 | if (!decodeCheck.contains(enc % length)) { 43 | System.out.println("取模 " + enc % length); 44 | decode[enc % length] = encode[i]; 45 | decodeCheck.add(enc % length); 46 | } else { 47 | for (int j = 0; j < length; j++) { 48 | if (!decodeCheck.contains(j)) { 49 | // 兜底,把 encode[i]后四位转成 j ^ i 的二进制值,确保decode的后四位不冲突 50 | encode[i] = (byte) (encode[i] & 0xF0 | (j ^ i)); 51 | decode[j] = encode[i]; 52 | decodeCheck.add(j); 53 | System.out.println("#取模 " + j); 54 | break; 55 | } 56 | } 57 | } 58 | } 59 | String encodeStr = ""; 60 | String decodeStr = ""; 61 | for (int i = 0; i < length; i++) { 62 | encodeStr += Byte.toUnsignedInt(encode[i]) + ","; 63 | decodeStr += Byte.toUnsignedInt(decode[i]) + ","; 64 | } 65 | System.out.println("encode: " + encodeStr); 66 | System.out.println("decode: " + decodeStr); 67 | } 68 | 69 | public static String encodeData(String inputString) throws NoSuchAlgorithmException, UnsupportedEncodingException { 70 | byte[] data = inputString.getBytes("utf-8"); 71 | byte[] res = encodeData(data); 72 | return Base64.getEncoder().encodeToString(res); 73 | } 74 | 75 | public static byte[] encodeData(byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException { 76 | // 翻转密码 77 | for (int i = 0; i < data.length; i++) { 78 | int index = Byte.toUnsignedInt(data[i]) % 16; 79 | int enc = encode[index] ^ data[i]; 80 | byte encdata = (byte) (enc & 0xFF); 81 | data[i] = encdata; 82 | } 83 | return data; 84 | } 85 | 86 | public static String decodeData(String base64Str) throws NoSuchAlgorithmException, UnsupportedEncodingException { 87 | byte[] data = Base64.getDecoder().decode(base64Str); 88 | byte[] res = decodeStr(data); 89 | return new String(res, "utf-8"); 90 | } 91 | 92 | public static byte[] decodeStr(byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException { 93 | // 翻转密码 94 | for (int i = 0; i < data.length; i++) { 95 | int index = Byte.toUnsignedInt(data[i]) % 16; 96 | int inx = decode[index] ^ data[i]; 97 | byte encdata = (byte) (inx & 0xFF); 98 | // System.out.println("@密文 " + byteToBinary(data[i]) + " 解密的秘钥 " + 99 | // byteToBinary(decode[index]) + " 解密的明文 " 100 | // + intToBinary(by) + " index " + index); 101 | data[i] = encdata; 102 | } 103 | return data; 104 | } 105 | 106 | public static String byteToBinary(byte b) { 107 | String result = ""; 108 | byte a = b; 109 | ; 110 | for (int i = 0; i < 8; i++) { 111 | byte c = a; 112 | a = (byte) (a >> 1);// 每移一位如同将10进制数除以2并去掉余数。 113 | a = (byte) (a << 1); 114 | if (a == c) { 115 | result = "0" + result; 116 | } else { 117 | result = "1" + result; 118 | } 119 | a = (byte) (a >> 1); 120 | } 121 | return result; 122 | } 123 | 124 | public static String intToBinary(int n) { 125 | String s = ""; 126 | while (n > 0) { 127 | s = ((n % 2) == 0 ? "0" : "1") + s; 128 | n = n / 2; 129 | } 130 | return s; 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /nodejs-proxy/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import Koa from 'koa' 4 | import Router from 'koa-router' 5 | import http from 'http' 6 | import crypto from 'crypto' 7 | import { httpProxy, httpClient } from './utils/httpClient.js' 8 | import bodyparser from 'koa-bodyparser' 9 | import FlowEnc from './utils/flowEnc.js' 10 | import levelDB from './utils/levelDB.js' 11 | import { webdavServer, alistServer } from './config.js' 12 | import { pathExec } from './utils/commonUtil.js' 13 | 14 | const webdavRouter = new Router() 15 | const restRouter = new Router() 16 | const app = new Koa() 17 | 18 | // ======================/proxy是实现本服务的业务============================== 19 | 20 | // bodyparser解析body 21 | const bodyparserMw = bodyparser({ enableTypes: ['json', 'form', 'text'] }) 22 | restRouter.all(/\/proxy\/*/, bodyparserMw) 23 | // TODO 24 | restRouter.all('/proxy/config', async (ctx) => { 25 | console.log('------proxy------', ctx.req.url) 26 | ctx.body = { success: true } 27 | }) 28 | app.use(restRouter.routes()).use(restRouter.allowedMethods()) 29 | 30 | // ======================下面是实现webdav代理的业务============================== 31 | 32 | // 可能是302跳转过来的下载的,/redirect?key=34233&decode=0 33 | webdavRouter.all('/redirect/:key', async (ctx) => { 34 | const request = ctx.req 35 | const response = ctx.res 36 | // 这里还是要encodeURIComponent ,因为http服务器会自动对url进行decodeURIComponent 37 | const data = await levelDB.getValue(ctx.params.key) 38 | if (data === null) { 39 | ctx.body = 'no found' 40 | return 41 | } 42 | const { webdavConfig, redirectUrl } = data 43 | console.log('@@redirect_url: ', request.url, redirectUrl) 44 | // 设置请求地址和是否要解密 45 | const flowEnc = new FlowEnc(webdavConfig.flowPassword) 46 | // 重新传回去 47 | const decode = ctx.query.decode 48 | request.url = decodeURIComponent(ctx.query.lastUrl) 49 | request.urlAddr = redirectUrl 50 | // 默认判断路径来识别是否要解密,如果有decode参数,那么则按decode来处理,这样可以让用户手动处理是否解密 51 | let decodeTransform = pathExec(webdavConfig.encPath, request.url) ? flowEnc.decodeTransform() : null 52 | if (decode) { 53 | decodeTransform = decode !== '0' ? flowEnc.decodeTransform() : null 54 | } 55 | delete request.headers.host 56 | // aliyun不允许这个referer,不然会出现403 57 | delete request.headers.referer 58 | request.webdavConfig = webdavConfig 59 | // 请求实际服务资源 60 | await httpProxy(request, response, null, decodeTransform) 61 | console.log('----finish 302---', decode, request.urlAddr, decodeTransform === null) 62 | }) 63 | 64 | // 创建middleware,闭包方式 65 | function proxyInit(webdavConfig, webdavProxy) { 66 | const { serverHost, serverPort, flowPassword, encPath } = webdavConfig 67 | const flowEnc = new FlowEnc(flowPassword) 68 | let authorization = webdavProxy 69 | return async (ctx, next) => { 70 | const request = ctx.req 71 | const response = ctx.res 72 | if (authorization) { 73 | // 缓存起来,提高效率 74 | request.headers.authorization = request.headers.authorization ? (authorization = request.headers.authorization) : authorization 75 | } 76 | request.headers.host = serverHost + ':' + serverPort 77 | request.urlAddr = `http://${request.headers.host}${request.url}` 78 | request.webdavConfig = webdavConfig 79 | const { method, headers, urlAddr } = request 80 | console.log('@@request_info: ', method, urlAddr, headers) 81 | // 如果是上传文件,那么进行流加密 82 | if (request.method.toLocaleUpperCase() === 'PUT' && pathExec(encPath, request.url)) { 83 | await httpProxy(request, response, flowEnc.encodeTransform()) 84 | return 85 | } 86 | await httpProxy(request, response) 87 | } 88 | } 89 | // 初始化webdav路由,这里可以优化成动态路由,只不过没啥必要,修改配置后直接重启就好了 90 | webdavServer.forEach((webdavConfig) => { 91 | if (webdavConfig.enable) { 92 | webdavRouter.all(new RegExp(webdavConfig.path), proxyInit(webdavConfig, true)) 93 | } 94 | }) 95 | /* =================================== 单独处理alist的逻辑====================================== */ 96 | 97 | // 初始化alist的路由,新增/d/* 路由 98 | const downloads = [] 99 | for (const key in alistServer.encPath) { 100 | downloads.push('/d' + alistServer.encPath[key]) 101 | downloads.push('/dav' + alistServer.encPath[key]) 102 | } 103 | alistServer.encPath = alistServer.encPath.concat(downloads) 104 | // 处理视频播放的问题 105 | webdavRouter.all('/api/fs/get', bodyparserMw, async (ctx, next) => { 106 | const request = ctx.req 107 | const response = ctx.res 108 | const { path } = ctx.request.body 109 | request.headers.host = alistServer.serverHost + ':' + alistServer.serverPort 110 | request.urlAddr = `http://${request.headers.host}${request.url}` 111 | request.webdavConfig = alistServer 112 | request.reqBody = JSON.stringify(ctx.request.body) 113 | // 判断打开的文件是否要解密,要解密则替换url,否则透传 114 | const respBody = await httpClient(request, response) 115 | const result = JSON.parse(respBody) 116 | if (pathExec(alistServer.encPath, path)) { 117 | // 修改返回的响应,匹配到要解密,就302跳转到本服务上进行代理流量 118 | console.log('@@getFile ', path, result) 119 | const key = crypto.randomUUID() 120 | await levelDB.putValue(key, { redirectUrl: result.data.raw_url, webdavConfig: alistServer }, 60 * 60 * 72) // 缓存起来,默认3天,足够下载和观看了 121 | result.data.raw_url = `/redirect/${key}?decode=1&lastUrl=${encodeURIComponent(path)}` 122 | } 123 | ctx.body = result 124 | }) 125 | 126 | webdavRouter.all(new RegExp(alistServer.path), proxyInit(alistServer)) 127 | // 使用路由控制 128 | app.use(webdavRouter.routes()).use(webdavRouter.allowedMethods()) 129 | 130 | const server = http.createServer(app.callback()) 131 | server.maxConnections = 1000 132 | const port = 5344 133 | server.listen(port, () => console.log('服务启动成功: ' + port)) 134 | setInterval(() => { 135 | console.log('server_connections', server._connections) 136 | }, 5000) 137 | --------------------------------------------------------------------------------