Fyoung密码算法

ufec2022-09-03

Fyoung密码算法

前言

Fyoung最近在部分学校更新了新的认证方式,貌似认证变简单了(使用的网页认证,没有什么加密),但是加强了检测,一旦检测到共享网络,就会封号一段时间,需要联系客服解封。

但博主的学校没有更新,依然使用的旧的认证方式,对于新飞Young还得靠其他大佬的破解,不过这个算法还是很有意思的,所以就记录一下。

当然本文只是记录了密码的算法,对于参数、日志等算法想必大家也不需要,所以就不记录了。

同时也在我的 GitHub 上开源了这个算法的实现,欢迎大家Star。

如果有需要参数、日志等的,可以私我,我会尽量提供。

算法

const md5 = require("js-md5");

const DateEnum = [
	"5084972163",
	"9801567243",
	"7286059143",
	"1850394726",
	"1462578093",
	"5042936178",
	"0145937682",
	"0964238571",
	"3497651802",
	"9125780643",
	"8634972150",
	"5924673801",
	"8274053169",
	"5841792063",
	"2469385701",
	"8205349671",
	"7429516038",
	"3769458021",
	"5862370914",
	"8529364170",
	"7936082154",
	"5786241930",
	"0728643951",
	"9418360257",
	"5093287146",
	"5647830192",
	"3986145207",
	"0942587136",
	"4357069128",
	"0956723814",
	"1502796384",
];

class MyEncrypt {
	CounterByteArr = [];
	keyToByteArr = [];
	byteArrLenth = 0;
	constructor(byteArr = []) {
		if (byteArr.length > 0 && byteArr.length < 256) {
			this.byteArrLenth = byteArr.length;
			for (let i = 0; i < 256; i++) {
				this.CounterByteArr[i] = i <= 127 ? i : i - 256;
				this.keyToByteArr[i] = byteArr[i % this.byteArrLenth];
			}
			let i = 0;
			for (let j = 0; j < 256; j++) {
				i = (i + this.CounterByteArr[j] + this.keyToByteArr[j]) & 255;
				let temp = this.CounterByteArr[i];
				this.CounterByteArr[i] = this.CounterByteArr[j];
				this.CounterByteArr[j] = temp;
			}
		}
	}

	static encryptPassword(key, password) {
		let strCharArr = this.strToCharArr(key);
		let this_instance = new MyEncrypt(strCharArr);
		let passwordToByte = this.strToUtf8Bytes(password);
		let password_bytes = this_instance.swapCounterXORPass(passwordToByte);
		let crypt_pass = this_instance.encodeChangePass(password_bytes);
		return crypt_pass.length === 32 ? crypt_pass.substring(8, 24) : crypt_pass;
	}

	static charToInt(char) {
		switch (char) {
			case "0":
				return 0;
			case "1":
				return 1;
			case "2":
				return 2;
			case "3":
				return 3;
			case "4":
				return 4;
			case "5":
				return 5;
			case "6":
				return 6;
			case "7":
				return 7;
			case "8":
				return 8;
			case "9":
				return 9;
			case "A":
				return 10;
			case "B":
				return 11;
			case "c":
				return 12;
			case "D":
				return 13;
			case "E":
				return 14;
			case "F":
				return 15;
			default:
				return 0;
		}
	}

	static strToCharArr(str) {
		let res = [];
		for (let i = 0; i < str.length; i++) {
			res.push(this.charToInt(str.charAt(i)));
		}
		return res;
	}

	static strToUtf8Bytes(str) {
		const utf8 = [];
		for (let ii = 0; ii < str.length; ii++) {
			let charCode = str.charCodeAt(ii);
			if (charCode < 0x80) utf8.push(charCode);
			else if (charCode < 0x800) {
				utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
			} else if (charCode < 0xd800 || charCode >= 0xe000) {
				utf8.push(
					0xe0 | (charCode >> 12),
					0x80 | ((charCode >> 6) & 0x3f),
					0x80 | (charCode & 0x3f)
				);
			} else {
				ii++;
				// Surrogate pair:
				// UTF-16 encodes 0x10000-0x10FFFF by subtracting 0x10000 and
				// splitting the 20 bits of 0x0-0xFFFFF into two halves
				charCode =
					0x10000 + (((charCode & 0x3ff) << 10) | (str.charCodeAt(ii) & 0x3ff));
				utf8.push(
					0xf0 | (charCode >> 18),
					0x80 | ((charCode >> 12) & 0x3f),
					0x80 | ((charCode >> 6) & 0x3f),
					0x80 | (charCode & 0x3f)
				);
			}
		}
		//兼容汉字,ASCII码表最大的值为127,大于127的值为特殊字符
		for (let jj = 0; jj < utf8.length; jj++) {
			var code = utf8[jj];
			if (code > 127) {
				utf8[jj] = code - 256;
			}
		}
		return utf8;
	}

	swapCounterXORPass(strBytes) {
		let res = [];
		let i = 0,
			j = 0;
		for (let k = 0; k < strBytes.length; k++) {
			i = (i + 1) & 255;
			j = (j + this.CounterByteArr[i]) & 255;
			// 交换值
			let temp = this.CounterByteArr[j];
			this.CounterByteArr[j] = this.CounterByteArr[i];
			this.CounterByteArr[i] = temp;
			let res_temp =
				this.CounterByteArr[
					(this.CounterByteArr[i] + this.CounterByteArr[j]) & 255
				] ^ strBytes[k];
			res[k] = res_temp <= 127 ? res_temp : res_temp - 256; // 转byte操作
		}
		return res;
	}

	unswapCounterXORPass(strBytes) {
		let res = [];
		let i = 0,
			j = 0;
		for (let k = 0; k < strBytes.length; k++) {
			i = (i + 1) & 255;
			j = (j + this.CounterByteArr[i]) & 255;
			// 交换值
			let temp = this.CounterByteArr[j];
			this.CounterByteArr[j] = this.CounterByteArr[i];
			this.CounterByteArr[i] = temp;
			let res_temp =
				strBytes[k] ^
				this.CounterByteArr[
					(this.CounterByteArr[i] + this.CounterByteArr[j]) & 255
				];
			res[k] = res_temp <= 127 ? res_temp : res_temp - 256; // 转byte操作
		}
		return res;
	}

	encodeMD5(bArr) {
		let str = "";
		for (const item of bArr) {
			let temp = (item & 255).toString(16);
			if (temp.length === 1) {
				temp = "0" + temp;
			}
			str += temp.toLowerCase();
		}
		return str;
	}

	encodeChangePass(password) {
		let md5s = md5.digest(password);
		md5s.forEach((item, index) => {
			if (item > 127) {
				md5s[index] = item - 256;
			}
		});
		return this.encodeMD5(md5s);
	}
}

module.exports = {
	MyEncrypt,
	DateEnum,
};

声明:代码中除了 strToUtf8Bytes 函数和 md5 库外,其他函数均为自己实现,strToUtf8Bytes 函数为网上找到的,并做了一部分修改,用于将字符串转为 UTF-8 字节码来兼容汉字,不过看起来好像有点儿多余,因为密码不会是汉字,但是为了保险起见还是加上了。

如需转载请注明出处

使用

使用也是非常简单的

const { MyEncrypt, DateEnum, DateEnum2 } = require("./fyoung.js");
let daykey = DateEnum[new Date().getDate() - 1];
console.log(MyEncrypt.encryptPassword(daykey, "你的Fyoung密码"));

结语

可能 java 版本的代码大家都有,就不发了,没什么意思。这里只是分享一下 js 版本的,go 语言版本不太好放出来(因为那已经是一个完整的项目了,不管会不会都可以非常方便的打包出可执行文件),后续会单独抽出沃派WeNet的加密算法,至于认证我就不发了,还是得要留一些门槛的。

新版 Fyoung 已经来临,不论是何种校园网,有压迫就有斗争,绝不能屈服,我们需要一个良心的网络环境

后浪们,加油!


fyoung 逆向


编辑此页面