diff --git a/.gitignore b/.gitignore index 5509140..db9d25b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.DS_Store +bin/* \ No newline at end of file diff --git a/HOW_DOES_IT_WORK.md b/HOW_DOES_IT_WORK.md new file mode 100644 index 0000000..c447ad3 --- /dev/null +++ b/HOW_DOES_IT_WORK.md @@ -0,0 +1,233 @@ +# Navicat Keygen - How does it work? + +[中文版 How does it work?](HOW_DOES_IT_WORK.zh-CN.md) + +## 1. Keyword Explanation. + +* __Navicat Activation Public Key__ + + It is an __RSA-2048__ public key that Navicat used to encrypt or decrypt offline activation information. + + It is stored in + + ``` + Navicat Premium.app/Contents/Resources/rpk + ``` + + You can see it by any kind of text editor. The content is: + + ``` + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I + qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv + a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF + R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 + WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt + YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ + awIDAQAB + -----END PUBLIC KEY----- + ``` + + If you have the corresponding private key, please tell me. I would be very appreciated for your generous. + + __NOTICE:__ + + Start from __Navicat Premuim 12.0.24 for Mac__, the public key is no longer stored in + + ``` + Navicat Premium.app/Contents/Resources/rpk + ``` + + Instead, the public key is stored in Navicat executable file + + ``` + Navicat Premium.app/Contents/MacOS/Navicat Premium + ``` + + in plaintext. You can see it by searching string `"-----BEGIN PUBLIC KEY----- "`. + + __NOTICE:__ + + Start from __Navicat Premium 12.1.14 for Mac__, the public key is still stored in the executable file in plaintext. + + However, it does not load the key from the plaintext. Instead, it loads the key from a piece of ciphertext which is 0x188 bytes long. The ciphertext is + + ```c + const uint8_t ciphertext[0x188] = { + 0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd, + 0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb, + 0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb, + 0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3, + 0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea, + 0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2, + 0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4, + 0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc, + 0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef, + 0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7, + 0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8, + 0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc, + 0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e, + 0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9, + 0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc, + 0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88, + 0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2, + 0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd, + 0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2, + 0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce, + 0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd, + 0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5, + 0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce, + 0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb, + 0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8 + }; + ``` + + The ciphertext is encrypted by XOR encryption where XOR key is + + ``` + \xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba + ``` + +* __Request Code__ + + It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the ciphertext of __Offline Activation Request Information__ encrypted by __Navicat Activation Public Key__. + +* __Offline Activation Request Information__ + + It is just a JSON-style UTF-8 string which contains 3 items. Respectively they are `"K"`, `"DI"` and `"P"`, which represent __snKey__, __DeviceIdentifier__, __Platform__. + + Example: + + ``` + { "K": "xxxxxxxxxxxxxxxx", "P": "Mac 10.13", "DI": "xxxxxxxxxxxxxxxxxxxx" } + ``` + +* __Activation Code__ + + It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the ciphertext of __Offline Activation Response Information__ encrypted by __Navicat Activation Private Key__ which, so far, we don't know. + +* __Offline Activation Response Information__ + + Just like __Offline Activation Request Information__, it is also a JSON-style UTF-8 string. But it contains 5 items. Respectively they are `"K"`, `"N"`, `"O"`, `"T"`, '`DI`'. + + `"K"` and `"DI"` has the same meaning mentioned in __Offline Activation Request Information__ and must be same with the corresponding items in __Offline Activation Request Information__. + + `"N"`, `"O"`, `"T"` represent __Name__, __Organization__, __Time__ respectively. __Name__ and __Organization__ are string and the type of __Time__ can be string or integer (Thanks for discoveries from @Wizr, issue #10). + + Differ from Navicat Windows version, `"T"` is mandatory and must have -1 ~ +4 days difference from current time. + + Example: + + ``` + { + "DI" : "xxxxxxxxxxxxxxxxxxxx", + "T" : "1515770827.925012", + "K" : "xxxxxxxxxxxxxxxx", + "N" : "DoubleLabyrinth", + "O" : "Shadow" + } + ``` + +* __snKey__ + + It is a 4-block-long string, while every block is 4-chars-long. + + __snKey__ is generated by 10-bytes-long data. In order to explain it easily, I use __uint8_t data[10]__ to represent the 10-bytes-long data. + + 1. __data[0]__ and __data[1]__ must be `0x68` and `0x2A` respectively. + + These two bytes are Naivcat signature number. + + 2. __data[2]__, __data[3]__ and __data[4]__ can be any byte. Just set them whatever you want. + + 3. __data[5]__ and __data[6]__ are related with your Navicat product language. + + | Language | data[5] | data[6] | Discoverer | + |------------|-----------|-----------|-----------------| + | English | 0xAC | 0x88 | | + | 简体中文 | 0xCE | 0x32 | | + | 繁體中文 | 0xAA | 0x99 | | + | 日本語 | 0xAD | 0x82 | @dragonflylee | + | Polski | 0xBB | 0x55 | @dragonflylee | + | Español | 0xAE | 0x10 | @dragonflylee | + | Français | 0xFA | 0x20 | @Deltafox79 | + | Deutsch | 0xB1 | 0x60 | @dragonflylee | + | 한국어 | 0xB5 | 0x60 | @dragonflylee | + | Русский | 0xEE | 0x16 | @dragonflylee | + | Português | 0xCD | 0x49 | @dragonflylee | + + 4. __data[7]__ is Navicat product ID. (Thanks @dragonflylee and @Deltafox79) + + |Product Name |Enterprise|Standard|Educational|Essentials| + |---------------------|:--------:|:------:|:---------:|:--------:| + |Navicat Report Viewer|0x0B | | | | + |Navicat Data Modeler | |0x47 |0x4A | | + |Navicat Premium |0x65 | |0x66 |0x67 | + |Navicat MySQL |0x68 |0x69 |0x6A |0x6B | + |Navicat PostgreSQL |0x6C |0x6D |0x6E |0x6F | + |Navicat Oracle |0x70 |0x71 |0x72 |0x73 | + |Navicat SQL Server |0x74 |0x75 |0x76 |0x77 | + |Navicat SQLite |0x78 |0x79 |0x7A |0x7B | + |Navicat MariaDB |0x7C |0x7D |0x7E |0x7F | + |Navicat MongoDB |0x80 |0x81 |0x82 | | + + 5. High 4 bits of __data[8]__ represents __major version number__. + + Low 4 bits is unknown, but we can use it to delay activation deadline. Possible values are `0000` or `0001`. + + __Example:__ + + For __Navicat 12 x64__: High 4 bits must be `1100`, which is the binary of number `12`. + For __Navicat 11 x64__: High 4 bits must be `1011`, which is the binary of number `11`. + + 6. __data[9]__ is unknown, but you can set it by `0xFD`, `0xFC` or `0xFB` if you want to use __not-for-resale license__. + + According to symbol information in __Navicat 12 for Mac x64__ version: + + * `0xFB` is __Not-For-Resale-30-days__ license. + * `0xFC` is __Not-For-Resale-90-days__ license. + * `0xFD` is __Not-For-Resale-365-days__ license. + * `0xFE` is __Not-For-Resale__ license. + * `0xFF` is __Site__ license. + + After that. Navicat use __DES__ with __ECB mode__ to encrypt the last 8 bytes which are from __data[2]__ to __data[9]__. + + The DES key is: + + ```cpp + const uint8_t DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; + ``` + + Then use Base32 to encode `uint8_t data[10]` whose encode table is + + ```cpp + // Thanks for discoveries from @Wizr, issue #10 + char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; + ``` + + After encoding, you will get a 16-char-long string starting with `"NAV"`. + + Finally, divide the 16-char-long string to four 4-chars-long blocks and join them with `"-"` then you will get __snKey__. + +## 2. Activation Process + +1. Check whether __snKey__ that user inputs is valid. + +2. After user clicks `Activate`, Navicat will start online activation first. If fails, user can choose offline activation. + +3. Navicat will use the __snKey__ that user inputs and some information collected from user's machine to generate __Offline Activation Request Information__. Then Navicat will encrypt it by __Navicat Activation Public Key__ and return a Base64-encoded string as __Request Code__. + +4. In legal way, the __Request Code__ should be sent to Navicat official activation server by a Internet-accessible computer. And Navicat official activation server will return a legal __Activation Code__. + +But now, we use keygen to play the official activation server's role. + +1. According to the __Request Code__, get `"DI"` value and `"K"` value. + +2. Fill __Offline Activation Response Information__ with `"K"` value, name, organization name, `"DI"` value and `"T"` value. + +3. Encrypt __Offline Activation Response Information__ by __Navicat Activation Private Key__ and you will get 256-byte-long data. + +4. Encode the 256-byte-long data by Base64. The result is __Activation Code__. + +5. After user input __Activation Code__, offline activation is done successfully. + diff --git a/HOW_DOES_IT_WORK.zh-CN.md b/HOW_DOES_IT_WORK.zh-CN.md new file mode 100644 index 0000000..c394f94 --- /dev/null +++ b/HOW_DOES_IT_WORK.zh-CN.md @@ -0,0 +1,230 @@ +# Navicat keygen - 注册机是怎么工作的? + +## 1. 关键词解释. + +* __Navicat激活公钥__ + + 这是一个2048位的RSA公钥,Navicat使用这个公钥来完成相关激活信息的加密和解密。 + + 这个公钥储存在 + + ``` + Navicat Premium.app/Contents/Resources/rpk + ``` + + 中,你可以用任何一种文本编辑器打开并查看它。这个公钥的具体内容为: + + ``` + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I + qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv + a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF + R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 + WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt + YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ + awIDAQAB + -----END PUBLIC KEY----- + ``` + + 如果您有相应的私钥并乐意公开的话欢迎联系我,我将非常感谢您的慷慨。 + + __注意:__ + + 从 __Navicat Premium for Mac 12.0.24__ 开始,公钥不再存储在 + + ``` + Navicat Premium.app/Contents/Resources/rpk + ``` + + 中。事实上,公钥放在了Navicat的二进制执行文件 + + ``` + Navicat Premium.app/Contents/MacOS/Navicat Premium + ``` + + 中,你可以通过搜索`"-----BEGIN PUBLIC KEY-----"`来找到它。 + + __注意:__ + + 从 __Navicat Premium for Mac 12.1.14__ 开始,公钥仍然以明文的方式储存在二进制可执行文件中。 + + 但是Navicat并不从这个明文中加载公钥。事实上,密钥是从一段0x188字节长的密文中加载的。密文为: + + ```c + const uint8_t ciphertext[0x188] = { + 0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd, + 0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb, + 0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb, + 0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3, + 0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea, + 0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2, + 0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4, + 0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc, + 0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef, + 0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7, + 0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8, + 0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc, + 0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e, + 0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9, + 0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc, + 0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88, + 0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2, + 0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd, + 0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2, + 0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce, + 0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd, + 0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5, + 0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce, + 0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb, + 0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8 + }; + ``` + + 这个密文是采用XOR加密得来,XOR密钥为 + + ``` + \xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba + ``` + +* __请求码__ + + 这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活信息__ 用 __Navicat激活公钥__ 加密的密文。 + +* __离线激活请求信息__ + + 这是一个JSON风格的UTF-8字符串。它包含了3个Key:`"K"`、`"DI"`和`"P"`,分别代表 __序列号__、__设备识别码__(与你的电脑硬件信息相关)和 __平台__ (其实就是操作系统类型)。 + + 例如: + + ``` + { "K": "xxxxxxxxxxxxxxxx", "P": "Mac 10.13", "DI": "xxxxxxxxxxxxxxxxxxxx" } + ``` + +* __激活码__ + + 这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活回复信息__ 用 __Navicat激活私钥__ 加密的密文。目前我们不知道官方的 __Navicat激活私钥__,所以我们得替换掉软件里的公钥。 + +* __离线激活回复信息__ + + 和 __离线激活请求信息__ 一样,它也是一个JSON风格的UTF-8字符串。但是它包含5个Key,分别为`"K"`、`"N"`、`"O"`、`"T"` 和 `"DI"`. + + `"K"` 和 `"DI"` 的意义与 __离线激活请求信息__ 中的相同,且Value必须与 __离线激活请求信息__ 中的相同。 + + `"N"`、`"O"`、`"T"` 分别代表 __注册名__、__组织__、__授权时间__。 + + __注册名__ 和 __组织__ 的值类型为UTF-8编码的字符串。__授权时间__ 的值类型可以为字符串或整数(感谢@Wizr在issue #10中的报告)。 + + 和Windows版本Navicat不同的是,`"T"` 项不可以省略,并且和当前时间的差距必须在-1 ~ +4天之内。 + + 例如: + + ``` + { + "DI" : "xxxxxxxxxxxxxxxxxxxx", + "T" : "1515770827.925012", + "K" : "xxxxxxxxxxxxxxxx", + "N" : "DoubleLabyrinth", + "O" : "Shadow" + } + ``` + +* __序列号__ + + 这是一个被分为了4个部分的字符串,其中每个部分都是4个字符长。 + + __序列号__ 是通过10个字节的数据来生成的。为了表达方便,我用 __uint8_t data[10]__ 来表示这10个字节。 + + 1. __data[0]__ 和 __data[1]__ 必须分别为 `0x68` 和 `0x2A`。 + + 这两个字节为Navicat的标志数。 + + 2. __data[2]__、__data[3]__ 和 __data[4]__ 可以是任意字节,你想设成什么都行。 + + 3. __data[5]__ 和 __data[6]__ 是Navicat的语言标志,值如下: + + | 语言类型 | data[5] | data[6] | 发现者 | + |------------|:---------:|:---------:|-----------------| + | English | 0xAC | 0x88 | | + | 简体中文 | 0xCE | 0x32 | | + | 繁體中文 | 0xAA | 0x99 | | + | 日本語 | 0xAD | 0x82 | @dragonflylee | + | Polski | 0xBB | 0x55 | @dragonflylee | + | Español | 0xAE | 0x10 | @dragonflylee | + | Français | 0xFA | 0x20 | @Deltafox79 | + | Deutsch | 0xB1 | 0x60 | @dragonflylee | + | 한국어 | 0xB5 | 0x60 | @dragonflylee | + | Русский | 0xEE | 0x16 | @dragonflylee | + | Português | 0xCD | 0x49 | @dragonflylee | + + 4. __data[7]__ 是Navicat产品ID。(感谢 @dragonflylee 和 @Deltafox79提供的数据) + + |产品名 |Enterprise|Standard|Educational|Essentials| + |---------------------|:--------:|:------:|:---------:|:--------:| + |Navicat Report Viewer|0x0B | | | | + |Navicat Data Modeler | |0x47 |0x4A | | + |Navicat Premium |0x65 | |0x66 |0x67 | + |Navicat MySQL |0x68 |0x69 |0x6A |0x6B | + |Navicat PostgreSQL |0x6C |0x6D |0x6E |0x6F | + |Navicat Oracle |0x70 |0x71 |0x72 |0x73 | + |Navicat SQL Server |0x74 |0x75 |0x76 |0x77 | + |Navicat SQLite |0x78 |0x79 |0x7A |0x7B | + |Navicat MariaDB |0x7C |0x7D |0x7E |0x7F | + |Navicat MongoDB |0x80 |0x81 |0x82 | | + + 5. __data[8]__ 的高4位代表 __版本号__。低4位未知,但可以用来延长激活期限,可取的值有`0000`和`0001`。 + + 例如: + + 对于 __Navicat 12__: 高4位必须是`1100`,为`12`的二进制形式。 + 对于 __Navicat 11__: 高4位必须是`1011`,为`11`的二进制形式。 + + 6. __data[9]__ 目前暂未知,但如果你想要 __not-for-resale license__ 的话可以设成`0xFD`、`0xFC`或`0xFB`。 + + 根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知: + + * `0xFB`是 __Not-For-Resale-30-days__ license. + * `0xFC`是 __Not-For-Resale-90-days__ license. + * `0xFD`是 __Not-For-Resale-365-days__ license. + * `0xFE`是 __Not-For-Resale__ license. + * `0xFF`是 __Site__ license. + + 之后Navicat使用 __ECB__ 模式的 __DES__ 算法来加密 __data[10]__ 的后8字节,也就是 __data[2]__ 到 __data[9]__ 的部分。 + + 相应的DES密钥为: + + ```cpp + const uint8_t DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; + ``` + + 之后使用Base32编码 __data[10]__,其中编码表改为: + + ```cpp + // Thanks for discoveries from @Wizr, issue #10 + char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; + ``` + + 编码之后你应该会得到一个16字节长的字符串,并且以"NAV"打头。 + + 将16字节的字符串分成4个4字节的小块,然后用`"-"`连接就可以得到 __序列号__。 + +## 2. 激活过程 + +1. 检查用户输入的 __序列号__ 是否合法。 + +2. 在用户点击了`激活`按钮之后,Navicat会先尝试在线激活。如果失败,用户可以选择离线激活。 + +3. Navicat会使用用户输入的 __序列号__ 以及从用户电脑收集来的信息生成 __离线激活请求信息__,然后用 __Navicat激活公钥__ 加密,并将密文用Base64编码,最后得到 __请求码__。 + +4. 正常流程下,__请求码__ 应该通过可联网的电脑发送给Navicat的官方激活服务器。之后Navicat的官方激活服务器会返回一个合法的 __激活码__。 + +但现在我们使用注册机来扮演官方激活服务器的角色,只是Navicat软件里的激活公钥得换成自己的公钥: + +1. 根据 __请求码__, 获得`"DI"`值和`"K"`值。 + +2. 用`"K"`值、用户名、组织名和`"DI"`值填写 __离线激活回复信息__。 + +3. 用自己的2048位RSA私钥加密 __离线激活回复信息__,你将会得到256字节的密文。 + +4. 用Base64编码这256字节的密文,就可以得到 __激活码__。 + +5. 在Navicat软件中填入 __激活码__ 即可完成离线激活。 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..068159e --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +CC = g++ +OPENSSL_INCLUDE_PATH = /usr/local/opt/openssl/include +OPENSSL_LIB_PATH = /usr/local/opt/openssl/lib + +OUTPUT_DIR = ./bin/ +PATCHER_DIR = ./navicat-patcher/ +KEYGEN_DIR = ./navicat-keygen/ +keygen_output = $(OUTPUB_DIR)navicat-keygen + +PATCHER_HEADER = \ +$(PATCHER_DIR)FileMapper.hpp \ +$(PATCHER_DIR)Helper.hpp \ +$(PATCHER_DIR)RSACipher.hpp \ +$(PATCHER_DIR)Solutions.hpp + +PATCHER_SOURCE = \ +$(PATCHER_DIR)Helper.cpp \ +$(PATCHER_DIR)main.cpp \ +$(PATCHER_DIR)Solution0.cpp \ +$(PATCHER_DIR)Solution1.cpp + +PATCHER_BINARY = $(OUTPUT_DIR)navicat-patcher + +KEYGEN_HEADER = \ +$(KEYGEN_DIR)Helper.hpp \ +$(KEYGEN_DIR)RSACipher.hpp \ +$(KEYGEN_DIR)DESCipher.hpp \ +$(KEYGEN_DIR)NavicatKeygen.hpp + +KEYGEN_SOURCE = \ +$(KEYGEN_DIR)Helper.cpp \ +$(KEYGEN_DIR)main.cpp + +KEYGEN_BINARY = $(OUTPUT_DIR)navicat-keygen + +patcher: $(PATCHER_HEADER) $(PATCHER_SOURCE) + @if [ ! -d $(OUTPUT_DIR) ]; then mkdir -p $(OUTPUT_DIR); fi + $(CC) -std=c++11 -O2 -I$(OPENSSL_INCLUDE_PATH) -L$(OPENSSL_LIB_PATH) -lcrypto $(PATCHER_SOURCE) -o $(PATCHER_BINARY) + +keygen: $(KEYGEM_HEADER) $(KEYGEN_SOURCE) + @if [ ! -d $(OUTPUT_DIR) ]; then mkdir -p $(OUTPUT_DIR); fi + $(CC) -std=c++11 -O2 -I$(OPENSSL_INCLUDE_PATH) -L$(OPENSSL_LIB_PATH) -lcrypto $(KEYGEN_SOURCE) -o $(KEYGEN_BINARY) + +all: patcher keygen + @echo 'Done.' + +.PHONY: all + +clean: +ifeq ($(wildcard $(PATCHER_BINARY)), $(PATCHER_BINARY)) + rm $(PATCHER_BINARY) +endif + +ifeq ($(wildcard $(KEYGEN_BINARY)), $(KEYGEN_BINARY)) + rm $(KEYGEN_BINARY) +endif + diff --git a/README.md b/README.md index f7ba978..abaf7a1 100644 --- a/README.md +++ b/README.md @@ -1,291 +1,189 @@ # Navicat Keygen - [中文版README](README.zh-CN.md) +[中文版README](README.zh-CN.md) - This repository will tell you how Navicat offline activation works. +This repository will tell you how Navicat offline activation works. -## 1. Keyword Explanation. +[How does it work?](HOW_DOES_IT_WORK.md) - * __Navicat Activation Public Key__ +## 1. How to build - It is a __RSA-2048__ public key that Navicat used to encrypt or decrypt offline activation information. +* Before you build keygen, you should make sure you have `OpenSSL` lib and `rapidjson` lib. + + If you have `brew`, you can install them by + + ```bash + $ brew install openssl + $ brew install rapidjson + ``` - It is stored in __Navicat Premium.app/Contents/Resources/rpk__. You can see it by any kind of text editor. The concrete content is: +* Clone `mac` branch and build keygen and patcher: - > -----BEGIN PUBLIC KEY----- - > MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I - > qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv - > a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF - > R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 - > WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt - > YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ - > awIDAQAB - > -----END PUBLIC KEY----- + ```bash + $ git clone -b mac https://github.com/DoubleLabyrinth/navicat-keygen.git + $ cd navicat-keygen + $ make all + ``` - If you have the corresponding private key, please tell me. I would be very appreciated for your generous. + You will see two executable files in `bin/` folder: - __NOTICE:__ + ```bash + $ ls bin/ + navicat-keygen navicat-patcher + ``` - Start from __Navicat Premuim 12.0.24 for Mac__, the public key is no longer stored in __Navicat Premium.app/Contents/Resources/rpk__. Instead, the public key is stored in Navicat executable file __Navicat Premium.app/Contents/MacOS/Navicat Premium__. You can see it by searching string `"-----BEGIN PUBLIC KEY----- "`. +## 2. How to Use - * __Request Code__ +1. Build keygen and patcher. - It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the cipher text of the __offline activation information__ encrypted by __Navicat Activation Public Key__. +2. Backup your `Navicat Premium.app/Contents/MacOS/Navicat Premium` and all of your saved database connection configurations (with password). - * __Offline Activation Request Information__ +3. Remove all connections, if have, that Navicat saved in `Keychain.app`. - It is just a JSON-style ASCII string which contains 3 items. Respectively they are `"K"`, `"DI"` and `"P"`, which represent __snKey__, __DeviceIdentifier__ (related with your machine), __Platform__ (Appropriately speaking, it should be OS Type). + You can find them by search with keyword `navicat`. - Like: - > { - >     "K": "xxxxxxxxxxxxxxxx", - >     "P": "Mac 10.13", - >     "DI": "xxxxxxxxxxxxxxxxxxxx" - > } +4. Use `navicat-patcher` to replace __Navicat Activation Public Key__. + + ``` + Usage: + navicat-patcher [RSA-2048 PrivateKey(PEM file)] + ``` - * __Activation Code__ + * ``: The path to Navicat executable file. + + __This parameter must be specified.__ - It is a Base64 string that represents 256-bytes-long data, while the 256-bytes-long data is the cipher text of the __offline activation response information__ encrypted by __Navicat Activation Private Key__ (so far, we don't know official activation private key). + * `[RSA-2048 PrivateKey(PEM file)]`: The path to an RSA-2048 private key file. + + __This parameter is optional.__ If not specified, `navicat-patcher` will generate a new RSA-2048 private key file `RegPrivateKey.pem` at current directory. - * __Offline Activation Response Information__ + __Example:__ - Just like __Offline Activation Request Information__, it is also a JSON-style ASCII string. But it contains 5 items. Respectively they are `"K"`, `"N"`, `"O"`, `"T"`, '`DI`'. + ``` + $ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium + ``` - `"K"` and `"DI"` has the same meaning mentioned in __Offline Activation Request Information__ and must be same with the corresponding items in __Offline Activation Request Information__. + __FOR Navicat Premium version < 12.0.24 ONLY:__ - `"N"`, `"O"`, `"T"` represent __Name__, __Organization__, __Time__ respectively. __Name__ and __Organization__ are string and the type of __Time__ can be string or integer (Thanks for discoveries from @Wizr, issue #10). + `navicat-patcher` will not modify target file. But you should use openssl to convert `RegPrivateKey.pem` to `rpk` file and replace - Differ from Navicat Windows version, `"T"` is mandatory and must be -1 ~ +4 days difference from current time. Here is an example of __Offline Activation Response Information__: + ``` + /Applications/Navicat Premium.app/Contents/Resources/rpk + ``` - > { - >     "DI" : "xxxxxxxxxxxxxxxxxxxx", - >     "T" : "1515770827.925012", - >     "K" : "xxxxxxxxxxxxxxxx", - >     "N" : "DoubleLabyrinth", - >     "O" : "Shadow" - > } + by it. - * __snKey__ + If you don't know how to use openssl, here's an example: - It is a 4-block-long string, while every block is 4-chars-long. + ```bash + $ openssl rsa -in RegPrivateKey.pem -pubout -out rpk + ``` - __snKey__ is generated by 10-bytes-long data. In order to explain it easily, I use __data[10]__ to represent the 10-bytes-long data. +5. __Generate a self-signed code-sign certificate and always trust it.__ - 1. __data[0]__ and __data[1]__ must be `0x68` and `0x2A` respectively. + __Then use `codesign` to re-sign `Navicat Premium.app`.__ - _`May change when Navicat product changes. Uncertain yet.`_ + ``` + $ codesign -f -s "Your self-signed code-sign certificate name" + ``` - 2. __data[2]__, __data[3]__ and __data[4]__ can be any byte. Just set them whatever you want. + __NOTICE:__ + + "Your self - signed code - sign certificate name" is the name of your certificate, not path. - 3. __data[5]__ and __data[6]__ are related with your Navicat product language. It depends. + __Example:__ - ~~_`May change when Navicat product changes. Uncertain yet.`_~~ - _`Must change when Navicat product changes. Confirmed yet.`_ + ```bash + $ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/ + ``` - | Language | data[5] | data[6] | Discoverer | - |------------|-----------|-----------|-----------------| - | English | 0xAC | 0x88 | | - | 简体中文 | 0xCE | 0x32 | | - | 繁體中文 | 0xAA | 0x99 | | - | 日本語 | 0xAD | 0x82 | @dragonflylee | - | Polski | 0xBB | 0x55 | @dragonflylee | - | Español | 0xAE | 0x10 | @dragonflylee | - | Français | 0xFA | 0x20 | @Deltafox79 | - | Deutsch | 0xB1 | 0x60 | @dragonflylee | - | 한국어 | 0xB5 | 0x60 | @dragonflylee | - | Русский | 0xEE | 0x16 | @dragonflylee | - | Português | 0xCD | 0x49 | @dragonflylee | +6. Then use `navicat-keygen` to generate __snKey__ and __Activation Code__. - According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that this two bytes are product signature. + ``` + Usage: + navicat-keygen + ``` - 4. __data[7]__ represents whether it is __commercial license__ or __non-commercial license__. + * ``: The path to a RSA-2048 private key file. + + __This parameter must be specified.__ - For __Navicat 12 x64__: `0x65` is __commercial license__, `0x66` is __non-commercial license__. - For __Navicat 11 x64__: `0x15` is __commercial license__, `0x16` is __non-commercial license__. + __Example:__ - _`May change when Navicat product changes. Uncertain yet.`_ - _`Must change when version change.`_ + ```bash + ./navicat-keygen ./RegPrivateKey.pem + ``` - According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that commercial license is __Enterprise License__ and non-commercial license is __Educational License__. + You will be asked to select Navicat language and input major version number. After that an randomly generated __snKey__ will be given. - 5. High 4 bits of __data[8]__ represents __version number__. Low 4 bits is unknown, but we can use it to delay activation deadline. Possible value is `0000` or `0001`. + ``` + Which is your Navicat Premium language? + 0. English + 1. Simplified Chinese + 2. Traditional Chinese + 3. Japanese + 4. Polish + 5. Spanish + 6. French + 7. German + 8. Korean + 9. Russian + 10. Portuguese - For __Navicat 12 x64__: High 4 bits must be `1100`, which is the binary of number `12`. - For __Navicat 11 x64__: High 4 bits must be `1011`, which is the binary of number `11`. + (Input index)> 1 + (Input major version number, range: 0 ~ 15, default: 12)> 12 - 6. __data[9]__ is unknown, but you can set it `0xFD` or `0xFC` or `0xFB` if you want to use __not-for-resale license__. This must not be `0x00`. But other value is OK. + Serial number: + NAVK-MWQR-LNXV-886V - According to __Navicat 12 for Mac x64__ version, what IDA 7.0 indicates is that: + Your name: + ``` - * `0xFB` is __Not-For-Resale-30-days__ license. - * `0xFC` is __Not-For-Resale-90-days__ license. - * `0xFD` is __Not-For-Resale-365-days__ license. - * `0xFE` is __Not-For-Resale__ license. - * `0xFF` is __Site__ license. + You can use this __snKey__ to activate your Navicat preliminarily. + + Then you will be asked to input `Your name` and `Your organization`. Just set them whatever you want, but not too long. - ----------------- + ```bash + Your name: DoubleLabyrinth + Your organization: DoubleLabyrinth + Input request code (in Base64), input empty line to end: + ``` + + After that, you will be asked to input the request code. Now __DO NOT CLOSE KEYGEN__. - After that. Navicat use __DES__ with __ECB mode__ to encrypt the last 8 bytes which are from __data[2]__ to __data[9]__. +7. __Disconnect your network__ and open Navicat Premium. - The DES key is: + Find and click `Registration`. Fill license key by __Serial number__ that the keygen gave and click `Activate`. - ```cpp - unsigned char DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; - ``` +8. Generally online activation will fail and Navicat will ask you do `Manual Activation`, just choose it. - Then encode the 10-bytes-long data: +9. Copy your request code and paste it in the keygen. Input empty line to tell the keygen that your input ends. - 1. Regard __data[10]__ as a 80-bits-long data. + ```bash + Your name: DoubleLabyrinth + Your organization: DoubleLabyrinth - If __data[10]__ starts with `0x68` and `0x2A`, so the 80-bits-long data is `01011000 00101010......` + Input request code (in Base64), input empty line to end: + q/cv0bkTrG1YDkS+fajFdi85bwNVBD/lc5jBYJPOSS5bfl4DdtnfXo+RRxdMjJtEcYQnvLPi2LF0 + OB464brX9dqU29/O+A3qstSyhBq5//iezxfu2Maqca4y0rVtZgQSpEnZ0lBNlqKXv7CuTUYCS1pm + tEPgwJysQTMUZf7tu5MR0cQ+hY/AlyQ9iKrQAMhHklqZslaisi8VsnoIqH56vfTyyUwUQXrFNc41 + qG5zZNsXu/NI79JOo7qTvcFHQT/k5cTadbKTxY+9c5eh+nF3JR7zEa2BDDfdQRLNvy4DTSyxdYXd + sAk/YPU+JdWI+8ELaa0SuAuNzr5fEkD6NDSG2A== - 2. Divide the 80-bits-long data as 16 5-bits-long blocks. + Request Info: + {"K":"NAVADHCNP2OIDV46", "DI":"Y2eJk9vrvfGudPG7Mbdn", "P":"MAC"} - If __data[10]__ starts with `0x68` and `0x2A`, so the 80-bits-long data is `01011`, `00000`, `10101`, `0....`, ... + Response Info: + {"K":"NAVADHCNP2OIDV46","DI":"Y2eJk9vrvfGudPG7Mbdn","N":"DoubleLabyrinth","O":"DoubleLabyrinth","T":1537630251} - 3. So the value every block is less than 32. Map them by a encode-table: + License: + oyoMYr9cfVGXeT7F1dqBwHsB/vvWj6SUL6aR+Kzb0lm5IyEj1CgovuSq+qMzFfx+ + oHMFaGKFg6viOY2hfJcrO2Vdq0hXZS/B/Ie3jBS2Ov37v8e3ufVajaH+wLkmEpLd + xppCVLkDQjIHYR2IPz5s/L/RuWqDpEY4TPmGFF6q+xQMnqQA3vXPyG+JYMARXLru + Y1gCDLN30v3DpyOeqKmFjUqiHK5h8s0NYiH2OpMyaCpi12JsF23miP89ldQp3+SJ + 8moo0cNGy7sFp2gX9ol2zVoo7qxfYlLl03f7CALJ6im0sx4yBsmlzFDdvpQUbXk8 + YZ5rT4LML2Fx6Wgnnklb5g== + ``` - ```cpp - // Thanks for discoveries from @Wizr, issue #10 - char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; - ``` +10. Finally, you will get __Activation Code__ which looks like a Base64 string. Just copy it and paste it in Navicat `Manual Activation` window, then click `Activate`. If nothing wrong, activation should be done successfully. - Then you will get a 16-char-long string. - - If __data[10]__ starts with `0x68` and `0x2A`, so after encoded, it should starts with `"N"`, `"A"`, `"V"`. - - 4. Divide the 16-char-long string to four 4-chars-long blocks, Then you get __snKey__. - -## 3. Activation Process - - 1. Check whether __sn_Key__ that user inputs is legal. - - 2. After user clicks `Activate`, Navicat will start online activation first. If fails, user can choose offline activation. - - 3. Navicat will use the __snKey__ that user inputs and some information collected from user's machine to generate __Offline Activation Request Information__, then encrypt it by __Navicat Activation Public Key__ and return Base64-encoded string as __Request Code__. - - 4. In legal way, the __Request Code__ should be sent to Navicat official activation server by a Internet-accessible computer. And Navicat official activation server will return a legal __Activation Code__. - - But now, we use keygen to play the official activation server's role. - - 1. According to the __Request Code__, Get `"DI"` value and `"K"` value. - - 2. Fill __Offline Activation Response Information__ with `"K"` value, name, organization name and `"DI"` value. - - 3. Encrypt __Offline Activation Response Information__ by __Navicat Activation Private Key__ and you will get 256-byte-long data. - - 4. Encode 256-byte-long data by Base64. The result is __Activation Code__. - - 5. Input __Activation Code__, then offline activation is done. - -## 4. How to build - - * Before you build keygen, you should make sure you have installed OpenSSL. - If you have `brew`, you can install it by `brew install openssl`. - - ```bash - $ cd navicat-keygen - $ make release - ``` - - * Build patcher if your Navicat version is or is after __12.0.24__. - - ```bash - $ cd navicat-patcher - $ make release - ``` - - __NOTICE:__ - - For Navicat whose version is or is after __12.0.24__, if you want to use your own RSA key, please replace the content in `/navicat-patcher/main.c` - - ```cpp - const char pubkey[9][72] = { - "-----BEGIN PUBLIC KEY-----", - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB", - "oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S", - "oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe", - "b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2", - "pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4", - "naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO", - "1QIDAQAB", - "-----END PUBLIC KEY-----" - }; - ``` - - with your own RSA public key before you build patcher. - -## 5. How to Use - 1. Build keygen. - - 2. Generate RSA-2048 private key and public key. __(Navicat Premium version < 12.0.24 ONLY)__ - - ```bash - $ openssl genrsa -out 2048key.pem 2048 - $ openssl rsa -in 2048key.pem -pubout -out rpk - ``` - - You will get two file: `2048key.pem` and `rpk`. - - Now you do not need to generate RSA key. I've already prepared these two file: - - * `rpk` file is in `navicat-patcher` folder. - - * `2048key.pem` is in `navicat-keygen` folder. - - 3. For Navicat Premium version < 12.0.24: - - * Replace `Navicat Premium.app/Contents/Resources/rpk` file by `rpk` file. - - For Navicat Premium version >= 12.0.24: - - * Backup your `Navicat Premium.app/Contents/MacOS/Navicat Premium` and __all of your saved database connection configurations (with password)__. - - * Delete all of passwords in `Keychain.app` that is saved by Navicat. - - * Run patcher: - - ```bash - $ cd navicat-patcher - $ ./navicat-patcher - ``` - - Example: - - ```bash - $ cd navicat-patcher - $ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium - ``` - - * __Generate a self-signed code-sign certificate and always trust it. Then use `codesign` to re-sign `Navicat Premium.app`.__ - - ```bash - $ codesign -f -s "Your self-signed code-sign certificate name" - ``` - - Note: "Your self - signed code - sign certificate name" is the name of Your certificate, not a path. - - Example: - - ```bash - $ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium - ``` - - 4. Then goto `navicat-keygen` folder and in Terminal: - - ```bash - $ ./navicat-keygen 2048key.pem - ``` - - You will get a __snKey__ and be asked to input your name and organization. - Just input and then you will be asked to input the request code. Now DO NOT CLOSE KEYGEN. - - 5. Open Navicat Premium, find and click `Registration`. Then input `Registration Key` by __snKey__ that keygen gave. Then click `Activate`. - - 6. Generally online activation will failed and Navicat will ask you do `Manual Activation`, just choose it. - - 7. Copy your request code and paste it in keygen. Leave empty line to tell keygen that your input ends (in other words, type `Enter` at least twice). - - 8. Then you will get activation code which looks like a Base64 string. Just copy it and paste it in Navicat `Manual Activation` window, then click `Activate`. If nothing is wrong, activation should be done successfully. - - 9. Finally, restore your database connection configurations if you have. diff --git a/README.zh-CN.md b/README.zh-CN.md index 62afe7a..64bd5a7 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,285 +1,184 @@ # Navicat Keygen - 这份repo将会告诉你Navicat是怎么完成离线激活的。 +这份repo将会告诉你Navicat是怎么完成离线激活的。 -## 1. 关键词解释 +[注册机是怎么工作的?](HOW_DOES_IT_WORK.zh-CN.md) - * __Navicat激活公钥__ +## 1. 如何编译 - 这是一个2048位的RSA公钥,Navicat使用这个公钥来完成相关激活信息的加密和解密。 +* 在编译之前,你应该确保你安装了`OpenSSL`和`rapidjson`。 + + 如果你有`brew`的话,你可以通过 + + ``` + $ brew install openssl + $ brew install rapidjson + ``` + + 来完成它们的安装。 - 这个公钥储存在 __Navicat Premium.app/Contents/Resources/rpk__ 中,你可以用任何一种文本编辑器打开并查看它。这个公钥的具体内容为: +* Clone `mac` 分支,并编译keygen和patcher - > -----BEGIN PUBLIC KEY----- - > MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I - > qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv - > a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF - > R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2 - > WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt - > YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ - > awIDAQAB - > -----END PUBLIC KEY----- - - 如果您有相应的私钥并乐意公开的话欢迎联系我,我将非常感谢您的慷慨。 - - __注意:__ - - 从 __Navicat Premium for Mac 12.0.24__ 开始,公钥不再存储在 __Navicat Premium.app/Contents/Resources/rpk__ 中。事实上,公钥放在了Navicat的二进制执行文件 __Navicat Premium.app/Contents/MacOS/Navicat Premium__ 中,你可以通过搜索`"-----BEGIN PUBLIC KEY-----"`来找到它。 - - * __请求码__ - - 这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活请求信息__ 被 __Navicat激活公钥__ 加密的密文。 - - * __离线激活请求信息__ - - 这是一个JSON风格的字符串。它包含了3个Key:`"K"`、`"DI"`和`"P"`,分别代表 __序列号__、__设备识别码__(与你的电脑硬件信息相关)和 __平台__ (其实就是操作系统类型)。 - - 例如: - > { - >     "K": "xxxxxxxxxxxxxxxx", - >     "P": "Mac 10.13", - >     "DI": "xxxxxxxxxxxxxxxxxxxx" - > } - - * __激活码__ - - 这是一个Base64编码的字符串,代表的是长度为256字节的数据。这256字节的数据是 __离线激活回复信息__ 被 __Navicat激活私钥__ 加密的密文,目前我们不知道官方的 __Navicat激活私钥__。 - - * __离线激活回复信息__ - - 和 __离线激活请求信息__ 一样,它也是一个JSON风格的字符串。但是它包含5个Key,分别为`"K"`、`"N"`、`"O"`、`"T"`和`"DI"`. - - `"K"` 和 `"DI"` 的意义与 __离线激活请求信息__ 中的相同,且Value必须与 __离线激活请求信息__ 中的相同。 - - `"N"`、`"O"`、`"T"` 分别代表 __注册名__、__组织__、__授权时间__。__注册名__ 和 __组织__ 的值类型为字符串,__授权时间__ 的值类型可以为字符串或整数(感谢@Wizr在issue #10的报告)。 - - 与Windows版本不同的是,`"T"`是必须的,且代表的时间必须位于当前时间-1 ~ +4天之内。下面是一个 __离线激活回复信息__ 的示例: - - > { - >     "DI" : "xxxxxxxxxxxxxxxxxxxx", - >     "T" : "1515770827.925012", - >     "K" : "xxxxxxxxxxxxxxxx", - >     "N" : "DoubleLabyrinth", - >     "O" : "Shadow" - > } - - * __序列号__ - - 这是一个被分为了4个部分的字符串,其中每个部分都是4个字符长。 - - __序列号__ 是通过10个字节的数据来生成的。为了表达方便,我用 __data[10]__ 来表示这10个字节。 - - 1. __data[0]__ 和 __data[1]__ 必须分别为 `0x68` 和 `0x2A`。 - - _`Navicat产品类型变化时,这两个值可能会变。目前暂未确认。`_ - - 2. __data[2]__、__data[3]__ 和 __data[4]__ 可以是任意字节,你想设成什么都行。 - - 3. __data[5]__ 和 __data[6]__ 与你Navicat的语言有关,值如下: - - | 语言类型 | data[5] | data[6] | 发现者 | - |------------|-----------|-----------|-----------------| - | English | 0xAC | 0x88 | | - | 简体中文 | 0xCE | 0x32 | | - | 繁體中文 | 0xAA | 0x99 | | - | 日本語 | 0xAD | 0x82 | @dragonflylee | - | Polski | 0xBB | 0x55 | @dragonflylee | - | Español | 0xAE | 0x10 | @dragonflylee | - | Français | 0xFA | 0x20 | @Deltafox79 | - | Deutsch | 0xB1 | 0x60 | @dragonflylee | - | 한국어 | 0xB5 | 0x60 | @dragonflylee | - | Русский | 0xEE | 0x16 | @dragonflylee | - | Português | 0xCD | 0x49 | @dragonflylee | - - 根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知这两个字节为 __Product Signature__。 - - 4. __data[7]__ 指示这是 __commercial license__ 还是 __non-commercial license__。 - - 对于 __Navicat 12__: `0x65`是 __commercial license__,`0x66`是 __non-commercial license__。 - 对于 __Navicat 11__: `0x15`是 __commercial license__,`0x16`是 __non-commercial license__。 - - _`Navicat产品类型变化时,这两个值可能会变。目前暂未确认。`_ - - 根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知:__commercial license__ 是 __Enterprise License__, __non-commercial license__ 是 __Educational License__。 - - 5. __data[8]__ 的高4位代表 __版本号__。低四位未知,但可以用来延长激活期限,可取的值有`0000`和`0001`。 - - 对于 __Navicat 12__: 高4位必须是`1100`,为`12`的二进制形式。 - 对于 __Navicat 11__: 高4位必须是`1011`,为`11`的二进制形式。 - - 6. __data[9]__ 目前暂未知,但如果你想要 __not-for-resale license__ 的话可以设成`0xFD`、`0xFC`或`0xFB`。这个值一定不能是`0x00`,其他值随便。 - - 根据 __Navicat 12 for Mac x64__ 版本残留的符号信息可知: - - * `0xFB`是 __Not-For-Resale-30-days__ license. - * `0xFC`是 __Not-For-Resale-90-days__ license. - * `0xFD`是 __Not-For-Resale-365-days__ license. - * `0xFE`是 __Not-For-Resale__ license. - * `0xFF`是 __Site__ license. - - ----------------- - - 之后Navicat使用 __ECB__ 模式的 __DES__ 算法来加密 __data[10]__ 的后8字节,也就是 __data[2]__ 到 __data[9]__ 的部分。 - - 相应的DES密钥为: - - ```cpp - unsigned char DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; - ``` - - 之后编码 __data[10]__: - - 1. 将 __data[10]__ 视作为一个80位长的数据。 - - 如果 __data[10]__ 以`0x68`和`0x2A`开始的话,80位长的数据应该为`01011000 00101010......` - - 2. 将80位长的数据分为16个5位长的块。 - - 如果 __data[10]__ 以`0x68`和`0x2A`开始的话,16个5位长的块应为`01011`、 `00000`、`10101`、`0....` - - 3. 这样每一块的值就会小于32。将它们通过下表编码: - - ```cpp - // Thanks for discoveries from @Wizr, issue #10 - char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; - ``` - - 你就会得到一个16字节的字符串。 - - 如果 __data[10]__ 以`0x68`和`0x2A`开始的话,编码之后应该以`"N"`、`"A"`、`"V"`打头。 - - 4. 将16字节的字符串分成4个4字节的小块,然后用`"-"`连接就可以得到 __序列号__。 - -## 3. 激活过程 - - 1. 检查用户输入的 __序列号__ 是否合法。 - - 2. 在用户点击了`激活`按钮之后,Navicat会先尝试在线激活。如果失败,用户可以选择离线激活。 - - 3. Navicat会使用用户输入的 __序列号__ 以及从用户电脑收集来的信息生成 __离线激活请求信息__,然后用 __Navicat激活公钥__ 加密,并将密文用Base64编码,最后得到 __请求码__。 - - 4. 正常流程下,__请求码__ 应该通过可访问Internet的电脑发送给Navicat的官方激活服务器。之后Navicat的官方激活服务器会返回一个合法的 __激活码__。 - - 但现在我们使用注册机来扮演官方激活服务器的角色,只是Navicat软件里的激活公钥得换成自己的公钥: - - 1. 根据 __请求码__, 获得`"DI"`值和`"K"`值。 - - 2. 用`"K"`值、用户名、组织名和`"DI"`值填写 __离线激活回复信息__。 - - 3. 用自己的2048位RSA私钥加密 __离线激活回复信息__,你将会得到256字节的密文。 - - 4. 用Base64编码这256字节的密文,就可以得到 __激活码__。 - - 5. 在Navicat软件中填入 __激活码__ 即可完成离线激活。 - -## 4. 如何编译 - - * 在编译之前,你应该确保你安装了OpenSSL。如果你有`brew`的话,你可以通过`brew install openssl`来完成OpenSSL的安装。 - - ```bash - $ cd navicat-keygen - $ make release - ``` - - * 如果你的Navicat版本号等于或大于12.0.24,你需要编译patcher。 - - ```bash - $ cd navicat-patcher - $ make release - ``` - - 注意: - - 对于Navicat版本号等于或大于12.0.24的,如果你想要使用自己的RSA密钥,请在编译patcher之前替换掉`navicat-patcher/main.c`里下面的内容 - - ```cpp - const char pubkey[9][72] = { - "-----BEGIN PUBLIC KEY-----", - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB", - "oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S", - "oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe", - "b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2", - "pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4", - "naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO", - "1QIDAQAB", - "-----END PUBLIC KEY-----" - }; + ```bash + $ git clone -b mac https://github.com/DoubleLabyrinth/navicat-keygen.git + $ cd navicat-keygen + $ make all ``` - 为你自己的RSA公钥。 + 编译完成后你会在 `bin/` 文件夹下看到两个可执行文件: -## 4. 如何使用这个Keygen + ```bash + $ ls bin/ + navicat-keygen navicat-patcher + ``` - 1. 编译好keygen。 +## 2. 如何使用这个Keygen - 2. 生成2048位的RSA密钥对。__(仅限Navicat Premium版本号小于12.0.24)__ +1. 编译好keygen和patcher。 - ```bash - $ openssl genrsa -out 2048key.pem 2048 - $ openssl rsa -in 2048key.pem -pubout -out rpk - ``` +2. 备份好 `Navicat Premium.app/Contents/MacOS/Navicat Premium` 以及Navicat中所有已保存的数据库连接(包括密码)。 - 你会得到两个文件:`2048key.pem`和`rpk`。 +3. 移除所有Navicat在 `Keychain.app` (即钥匙链)中保存的连接,如果有的话。 - __现在你们可以不用生成RSA密钥了,我已经准备好了这两个文件:__ + 你可以通过搜索关键词 `navicat` 来找到它们。 - * `rpk`文件在`navicat-patcher`文件夹中。 +4. 使用`navicat-patcher`替换掉公钥: - * `2048key.pem`在`navicat-keygen`文件夹中。 + ``` + Usage: + navicat-patcher [RSA-2048 PrivateKey(PEM file)] + ``` - 3. 对于Navicat Premium版本 < 12.0.24的: + * ``: Navicat可执行文件的路径。 + + __这个参数必须指定。__ - * 用生成或提供的`rpk`文件替换掉`Navicat Premium.app/Contents/Resources/rpk`。 + * `[RSA-2048 PrivateKey(PEM file)]`: RSA-2048私钥文件的路径。 + + __这个参数是可选的。__ 如果没有指定,`navicat-patcher`将会在当前目录下生成一个新的RSA-2048私钥文件`RegPrivateKey.pem`。 - 对于Navicat Premium版本 >= 12.0.24的: + __例如:__ - * 备份好`Navicat Premium.app/Contents/MacOS/Navicat Premium`文件,__以及Navicat中所有的数据库连接配置信息(包括密码)__。 + ``` + $ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium + ``` - * 删掉`Keychain.app`中所有由Navicat保存的密码。 + __仅对 Navicat Premium 版本 < 12.0.24 的说明:__ - * 运行patcher: + 如果你的Navicat版本小于12.0.24,那么`navicat-patcher`将不会修改目标文件。但你必须使用openssl将`RegPrivateKey.pem`转化为`rpk`文件,并用转化得到的文件替换 - ```bash - $ cd navicat-patcher - $ ./navicat-patcher - ``` + ``` + /Applications/Navicat Premium.app/Contents/Resources/rpk + ``` - 例如: + 如果你不知道怎么转化,这是一份样例: - ```bash - $ cd navicat-patcher - $ ./navicat-patcher /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium - ``` + ```bash + $ openssl rsa -in RegPrivateKey.pem -pubout -out rpk + ``` - * __生成一个自签名的代码签名证书,并在`Keychain.app`中总是信任它。然后使用`codesign`命令对`Navicat Premium.app`进行重签名。这一步非常重要。__ +5. __生成一份自签名的代码证书,并总是信任该证书。这一步非常重要。__ - ```bash - $ codesign -f -s "Your self-signed code-sign certificate name" - ``` + __然后用`codesign`对`Navicat Premium.app`重签名。__ - 注意:"Your self-signed code-sign certificate name"是你证书的名字,不是路径。 - - 例如: + ```bash + $ codesign -f -s "Your self-signed code-sign certificate name" + ``` - ```bash - $ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/Contents/MacOS/Navicat\ Premium - ``` + __注意:__ + + "Your self-signed code-sign certificate name"是你证书的名字,不是路径。 - 4. 接下来,打开`Terminal.app`,并定位到`navicat-keygen`文件夹: + __例如:__ - ```bash - $ ./navicat-keygen 2048key.pem - ``` + ```bash + $ codesign -f -s "foobar" /Applications/Navicat\ Premium.app/ + ``` - 接下来你会被要求输入Navicat的语言版本,然后得到一个 __序列号__,同时keygen会要求你输入用户名和组织名。 - 直接填写,之后你会被要求填写你得到的 __请求码__。注意此时 __不要关闭Terminal__. +6. 接下来使用`navicat-keygen`来生成 __序列号__ 和 __激活码__。 - 5. 打开Navicat Premium。找到`注册`按钮并点击,在弹出的窗口中填入keygen给你的 __序列号__。然后点击`激活`按钮。 + ``` + Usage: + navicat-keygen + ``` - 6. 一般来说在线激活肯定会失败,这时候Navicat会询问你是否`手动激活`,直接选吧。 + * ``: RSA-2048私钥文件的路径。 + + __这个参数必须指定。__ - 7. 在`手动激活`窗口你会得到一个请求码,复制它并把它粘贴到keygen里。最后别忘了连按至少两下回车结束输入。 + __例如:__ - 8. 如果不出意外,你会得到一个看似用Base64编码的 __激活码__。直接复制它,并把它粘贴到Navicat的`手动激活`窗口,最后点`激活`按钮。如果没什么意外的话应该能成功激活。 + ```bash + ./navicat-keygen ./RegPrivateKey.pem + ``` - 9. 最后,如果你备份了数据库连接配置信息,那么恢复它把。 + 你会被要求选择Navicat的语言以及输入主版本号。之后会随机生成一个 __序列号__。 + + ``` + Which is your Navicat Premium language? + 0. English + 1. Simplified Chinese + 2. Traditional Chinese + 3. Japanese + 4. Polish + 5. Spanish + 6. French + 7. German + 8. Korean + 9. Russian + 10. Portuguese + + (Input index)> 1 + (Input major version number, range: 0 ~ 15, default: 12)> 12 + + Serial number: + NAVK-MWQR-LNXV-886V + + Your name: + ``` + + 你可以使用这个 __序列号__ 暂时激活Navicat。 + + 接下来你会被要求输入`用户名`和`组织名`;请随便填写,但不要太长。 + + ```bash + Your name: DoubleLabyrinth + Your organization: DoubleLabyrinth + Input request code (in Base64), input empty line to end: + ``` + + 之后你会被要求填入请求码。注意 __不要关闭注册机__. + +7. __断开网络__ 并打开Navicat。找到`注册`窗口,填入注册机给你的序列号。然后点击`激活`按钮。 + +8. 一般来说在线激活肯定会失败,这时候Navicat会询问你是否`手动激活`,直接选吧。 + +9. 在`手动激活`窗口你会得到一个请求码,复制它并把它粘贴到keygen里。最后别忘了连按至少两下回车结束输入。 + + ```bash + Your name: DoubleLabyrinth + Your organization: DoubleLabyrinth + + Input request code (in Base64), input empty line to end: + q/cv0bkTrG1YDkS+fajFdi85bwNVBD/lc5jBYJPOSS5bfl4DdtnfXo+RRxdMjJtEcYQnvLPi2LF0 + OB464brX9dqU29/O+A3qstSyhBq5//iezxfu2Maqca4y0rVtZgQSpEnZ0lBNlqKXv7CuTUYCS1pm + tEPgwJysQTMUZf7tu5MR0cQ+hY/AlyQ9iKrQAMhHklqZslaisi8VsnoIqH56vfTyyUwUQXrFNc41 + qG5zZNsXu/NI79JOo7qTvcFHQT/k5cTadbKTxY+9c5eh+nF3JR7zEa2BDDfdQRLNvy4DTSyxdYXd + sAk/YPU+JdWI+8ELaa0SuAuNzr5fEkD6NDSG2A== + + Request Info: + {"K":"NAVADHCNP2OIDV46", "DI":"Y2eJk9vrvfGudPG7Mbdn", "P":"MAC"} + + Response Info: + {"K":"NAVADHCNP2OIDV46","DI":"Y2eJk9vrvfGudPG7Mbdn","N":"DoubleLabyrinth","O":"DoubleLabyrinth","T":1537630251} + + License: + oyoMYr9cfVGXeT7F1dqBwHsB/vvWj6SUL6aR+Kzb0lm5IyEj1CgovuSq+qMzFfx+ + oHMFaGKFg6viOY2hfJcrO2Vdq0hXZS/B/Ie3jBS2Ov37v8e3ufVajaH+wLkmEpLd + xppCVLkDQjIHYR2IPz5s/L/RuWqDpEY4TPmGFF6q+xQMnqQA3vXPyG+JYMARXLru + Y1gCDLN30v3DpyOeqKmFjUqiHK5h8s0NYiH2OpMyaCpi12JsF23miP89ldQp3+SJ + 8moo0cNGy7sFp2gX9ol2zVoo7qxfYlLl03f7CALJ6im0sx4yBsmlzFDdvpQUbXk8 + YZ5rT4LML2Fx6Wgnnklb5g== + ``` + +10. 如果不出意外,你会得到一个看似用Base64编码的激活码。直接复制它,并把它粘贴到Navicat的`手动激活`窗口,最后点`激活`按钮。如果没什么意外的话应该能成功激活。 diff --git a/navicat-activate-server/cert-crt.pem b/navicat-activate-server/cert-crt.pem new file mode 100644 index 0000000..2323427 --- /dev/null +++ b/navicat-activate-server/cert-crt.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEujCCAqICCQD77KLUs6JamDANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRh +Y3RpdmF0ZS5uYXZpY2F0LmNvbTAeFw0xODEyMTcwODEzMDNaFw0yODEyMTQwODEz +MDNaMB8xHTAbBgNVBAMMFGFjdGl2YXRlLm5hdmljYXQuY29tMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAvGZxEqPRuMtzCS3G+PzLsmxmBTE4VNiSEeDp +MZcZOQo7egwCkZOrh3u32tJwFp0p++Gv3QiTQ709vT3h0d54uLm4oZ3Ov/X7xFQ7 +Jy2qz59sil4hZTnjD9UTb6zGP2ww7LYLAkngX8+heECiJtPGckBMwfM91azR478w +BZS0Kwv2b+jlN9htzXfn00aRXhGc0x5LMTibW7ufwYjPpugmdq5JsK2pS8l75cbJ +qxZgC38l+WKrvyfTcoqzkw0fcaZIabmVsRWy2qu6O4UQ7LeII59Cl397HmjyUvis +GWdhpdQvMYpOczWnARm2+PBxMkK3nOEAvAa5LxHEf1mhMdD5t50i0Rw8rGwsfIAq +btAUDPKYhLlC7Hr+3Bijrzrq8Q9L+znlKgeCC/9eJqv9KGTLBjuB0y5MFD48hCob +Q18sZdaVXIDZD7xil/GtFayho7CKdNL/m2O/1Ek03OJgqCSXPP243nKnSKWhOfxT +vIsOaPBThdYGLp+jnbHPIRSb5TcXiTYh7dtqg9QEEzclPMXPTZEwA+TVKbQekzX3 +Mv+CsJ9xzHxKX0/tY7IH3++MeEeOY3s2sNQjayS+3YdsCEYBtYhFcd+SoUyk2BXu +TpnLIkIQA9j6W+mpAVGvEF4ZGB55Mvic20gByVf4RzrT8Ws9gguWJmffNyD7qPCI +d00r6hECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEApX8giNz2VERIxpcnVphtOfqA +37HJcDvmDxlnpSSUvVrCFTM+YD/uAuecE1GmQIxuwRbSb63s/vRetboHZ7A+PSkr +BwR6xkIlJwOVwaiZXs10HmP5Nz8aele+V83fCAWY/mtxn6EF0Pi7sxTCLg5PSdR+ +P7QRr6/vZPoyFd8TcU5c3VFMqa/Z3Sdhmx6XWYs2COOFTfOo4k8sXYIwGVyWOt4/ +XHqpouhpeF+/oxlaOo/fnJY2IcGsao3eyWevZMCrPsf/MQqeHJhsTG6yLfU7pldS +s25LbXNzF8mjfQQLWRCKCGgUqRUGjJBsIyg8X9C9e0MKL+/W7KDlcJA8VyJIjFmK +k1aEyKfLzNTkAZtzGutYL4x0lAJBxfYWbm9WVtrU2QMkqkkjru1buPRyw0vu3QgO +hizSWkU1JqERTADsRN/u8VTddmdLy0Bs44q/btnBO7EU86+lg9O1mPGpD4KmVB2e +2XWzfQ++059sA5vT2LtaQuvFG4nulnQtNeboVyzNam+zff7vrHXrszFk/0YFTw0n +bS4YHVlpYDIH7KY47L42sj/48LjmDgQ0AExFkkzPCFjaPNxIm5vsg9nm6H3iZOjx +9zoMl9EHIwfJqNxw0fsdATBJbW/MsWJ+DHiiyQVPisWC11BmUqKWh/05bLHBhKEB +7wKbi9kxjRflM5+An+g= +-----END CERTIFICATE----- diff --git a/navicat-activate-server/cert-key.pem b/navicat-activate-server/cert-key.pem new file mode 100644 index 0000000..c18983e --- /dev/null +++ b/navicat-activate-server/cert-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC8ZnESo9G4y3MJ +Lcb4/MuybGYFMThU2JIR4Okxlxk5Cjt6DAKRk6uHe7fa0nAWnSn74a/dCJNDvT29 +PeHR3ni4ubihnc6/9fvEVDsnLarPn2yKXiFlOeMP1RNvrMY/bDDstgsCSeBfz6F4 +QKIm08ZyQEzB8z3VrNHjvzAFlLQrC/Zv6OU32G3Nd+fTRpFeEZzTHksxOJtbu5/B +iM+m6CZ2rkmwralLyXvlxsmrFmALfyX5Yqu/J9NyirOTDR9xpkhpuZWxFbLaq7o7 +hRDst4gjn0KXf3seaPJS+KwZZ2Gl1C8xik5zNacBGbb48HEyQrec4QC8BrkvEcR/ +WaEx0Pm3nSLRHDysbCx8gCpu0BQM8piEuULsev7cGKOvOurxD0v7OeUqB4IL/14m +q/0oZMsGO4HTLkwUPjyEKhtDXyxl1pVcgNkPvGKX8a0VrKGjsIp00v+bY7/USTTc +4mCoJJc8/bjecqdIpaE5/FO8iw5o8FOF1gYun6Odsc8hFJvlNxeJNiHt22qD1AQT +NyU8xc9NkTAD5NUptB6TNfcy/4Kwn3HMfEpfT+1jsgff74x4R45jezaw1CNrJL7d +h2wIRgG1iEVx35KhTKTYFe5OmcsiQhAD2Ppb6akBUa8QXhkYHnky+JzbSAHJV/hH +OtPxaz2CC5YmZ983IPuo8Ih3TSvqEQIDAQABAoICAQCZ67v/tZV3O8itPBgufiJR +kFw9a7wRHashLjZ2zHLP9jsneD50/0eJdht8jKcE4rxVTEqo9AOUuTyCqtce5nqM +uTdN2yb6EBb0jLiTRq0h2acM2ODB2exXmAa8G1UQpezGx+fwo7jLrk/Gdk/EFwsv +drb/UiI3u2zze9TZCme0L57USVtUJ991mbvuEd5cE1mj5kEaYpwS8xQPQx4bx9j8 +52HPFeKVx3QRAzrqK2qmmefFesbEct4+23Dg+DyzVl4c+oGA3zBzlQ+e5xUvwLZa +HBiEu6Mz8hvgi8fXLW5K1DMGb2+ukqvxqzeg5JgmrvliEzXNaFTNTt/SB8E5ePk6 +kQ6K4+2rvBFN6Bho/3j649ZbYQiEQ7LL2nm6kLT2QkFcNGuj+PPv97XbR/sUKnG/ +0FAKisgvNEaVGz6GV1kxqZ0SZkohkSknGODbh03KxKEykUhx05DkDJ9z4vhguOaF +tOu+77FmxRpWCyUEzMvXXHuNyryW1k6WGZU7N142gB1HxnMB1OsP9nqTELmHSuh8 +0z8zgMH/CNVPD6JGLQWTuDjI99oFwuI4eKFYcKqrvquO5KBPOfu65l0siE8jnGgI +f9JwkDAMqB2Ho51m241H9XPH1704Y3a2Ew7NnWbZvCKgtmbGw8doIbTur+gKCI0+ +LqaFyLu35g6Z1jVfijeW7QKCAQEA7bEjK6MjrgYsucx1nnzQ+44SNSGnsTnFLK/P +WXYA5yxIUT9eabJf/VwtN0NGoTMmSN8UHFtvsyvH01TQpToUy6jU82O0bEtpDYvE +qKvp1w224+c5TCOvzq4kyBRtAito5BFwwLWBCMZ9k4+GADw4MDNgr1Hyt94a97JD +uPLbhNOJcapxNV+9nAEN61d3jhvR6YMVJ0npnnqwub/rNv7VJhyBz35p8i1nTkve +6YHe8uMvN/TXgryaoP1igrvO9T5xrVlv7nQnzNBjcRJqr99sYMdZ2AOTEQnqiXnU +kUME15CtDgdEarlkpwNAuQfq/VxOpMcEkZw4zhvAzXAAwR89swKCAQEAyulbpy+7 +hTl14RBV9Y2kM25XCWRlN9GNLvfN8vfC9rix98pa+SWg9wawUZPnckZjM3vgOUpm +fZVZocBCK0cT5Fm3wUPX3dmNXhlPj6t+HukXto61N9sQoliz0RYzOGFOlT2+1bYF +pEKQW5MnRGe7PIwYBUbdsm9nbO7XflEz+F5h3pZmnMWXQVZdMTyQrMKnsifh5y+T +ls2Bboy2PLpquEQH2FWbPHaaLzMLy6wD5IAR+c/rFWIk0jl8BlvMOs3sPYPxXgIO +TdAWOCOSlymHbB3tot1lUAWfxjNO7vx0EB5mAG1kl5dU2IVzCU1o2PQUCFph9hQF +FxfFWF3h8Iy/KwKCAQBN2olkj6juJUOdD+Uupj9lReGc2Ystt3hn7KYD7WzaS1zc +j98Cy9Q5DmoZAcYuoqxVjgvuRdzOWPa4t70ngWCyXHhzyCKengyozTD69mQ/dlSD +TYcs8ztwfQW7K0WRQlq26Sd/V3QORcV4AbiodxEIaKuwgSz5IJzAqPkZqSJR3V/V +hThTfSqD4KyIezvDpkUZMUBKgCEvMYyVKtGGT+3M7+Vs60EUe96sfJlk7o7yC4u5 +zk6Qmeoj9RrBK3bcSTm+x96rwgwPP+pybM/ZD1tWT7WT1YfkOtVBdlAzzDnXyaGF +SMgWFOds9NnMQVWs6wEvY6iRcRQCHoEGSTtWBj+VAoIBAH/9wMVtk3BOtW94+W/B +n5CdiiMvg7U45kB5KRyYcg9ko0W1C7tao/UN8AwVKonnok1oVyBsMMgIfBhOiK55 +0C9a9FDhNXoH72cqugfa2dwvlbievep0sUJmh05dHYPzxTjYUNbHUTmyjxJkeQFq +GS3xBHnXoKIcKXYKFj3khM6m4j8gSaub63PYQUJ8fJms01DKXXGRjhwylvEfqxgV +UpnWt8ga/6ec/FbHcSpQ38ezjSMxpoy/cpB0mCn+n418NQb8gjSVQWaN2mYg8ieN +l5F4M1xnIK/gTE8eMC0Ja0B7nEWquyfv2iIV02FLxdYL59L5CA/LwwY1BVNfLNSq +Qy8CggEBAMn9Dco7uBUgE4M9EQzic9oWlrsxno+XklhRtMkWhQfdbFJbZIRs7rXO +SSx6en7IBp7L/Xqb1TS+u32sdnjUqnqCXJUlkgn5IFwEoLCFlHFvgE41hnmhlRr/ +xgXsPYozKDq65ZdZE4wuWXTMqbCZTxI1bF2MZ0Jvi5GePLQaVhpbQFsNFskjYYcg +2VGaFWlDQtc2luTuvHiHsMwXx9kC4sWE6k01Nr60c8oRbVEM+UeJ1bspuk2TtLU3 +cmGBdOGiqYxvbKU8+ROeimbCMwtf5lA0zYrXTW8TnZBibcv+FhFrqtJz6w54h1NR +6VJ+97E5OdzZqH2XTdf9Fi7c/WjxYCg= +-----END PRIVATE KEY----- diff --git a/navicat-activate-server/server.py b/navicat-activate-server/server.py new file mode 100755 index 0000000..91c043a --- /dev/null +++ b/navicat-activate-server/server.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +from http.server import HTTPServer, BaseHTTPRequestHandler +import sys, ssl, base64, json, time, gzip + +from OpenSSL import crypto +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization + +openssl_lib = crypto._lib + +def RSAPublicEncrypt(key, msg: bytes): + enc = bytes((key.key_size + 7) // 8) + length = openssl_lib.RSA_public_encrypt(len(msg), msg, enc, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING) + if (length == -1): + raise ValueError('Public encrypt failure.') + return enc[:length] + +def RSAPublicDecrypt(key, enc: bytes): + msg = bytes((key.key_size + 7) // 8) + length = openssl_lib.RSA_public_decrypt(len(enc), enc, msg, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING) + if (length == -1): + raise ValueError('Public decrypt failure.') + return msg[:length] + +def RSAPrivateEncrypt(key, msg: bytes): + enc = bytes((key.key_size + 7) // 8) + length = openssl_lib.RSA_private_encrypt(len(msg), msg, enc, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING) + if (length == -1): + raise ValueError('Private encrypt failure.') + return enc[:length] + +def RSAPrivateDecrypt(key, enc: bytes): + msg = bytes((key.key_size + 7) // 8) + length = openssl_lib.RSA_private_decrypt(len(enc), enc, msg, key._rsa_cdata, openssl_lib.RSA_PKCS1_PADDING) + if (length == -1): + raise ValueError('Private decrypt failure.') + return msg[:length] + +def LoadKey(path: str): + with open(path, 'rb') as f: + key = serialization.load_pem_private_key(f.read(), None, default_backend()) + return key + +class NavicatActivateServerHandler(BaseHTTPRequestHandler): + + def do_GET(self): + self.send_response(200) + self.end_headers() + self.wfile.write('This is an activation server for Navicat.'.encode()) + + def do_POST(self): + print('-' * 32) + print(self.headers) + body = self.rfile.read(int(self.headers['Content-Length'], 0)) + print('Receive a POST request from %s:%d -->' % self.client_address) + print(body) + if (body.startswith(b'input:')): + enc = body[6:] + try: + snKey = RSAPrivateDecrypt(prikey, enc).decode() + print('snKey = %s' % snKey) + license = {} + license['K'] = snKey + license['DI'] = 'fjavsmSJmOssbuo4Ns9H' + license['N'] = 'DoubleLabyrinth' + license['O'] = 'DoubleLabyrinth' + license['T'] = 1545208026 + license['M'] = 'MMMMMMMM' + license['FA'] = 'FAFAFAFA' + license = json.dumps(license).encode() + print(license) + data = gzip.compress(RSAPrivateEncrypt(prikey, license)) + self.send_response(200) + self.send_header('Vary', 'Accept-Encoding') + self.send_header('Content-Encoding', 'gzip') + self.send_header('Content-Type', 'text/html; charset=UTF-8') + self.send_header('Content-Length', len(data)) + self.end_headers() + self.wfile.write(data) + print('sent...') + print() + except: + self.send_response(404) + self.end_headers() + pass + +IP = '0.0.0.0' +Port = 443 +prikey = LoadKey(sys.argv[1]) + +httpd = HTTPServer((IP, Port), NavicatActivateServerHandler) +httpd.socket = ssl.wrap_socket(httpd.socket, + keyfile = 'cert-key.pem', + certfile = 'cert-crt.pem', + server_side = True) +httpd.serve_forever() + diff --git a/navicat-keygen/2048key.pem b/navicat-keygen/2048key.pem deleted file mode 100644 index c9a0530..0000000 --- a/navicat-keygen/2048key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAxqkTcfbKw8ysVygePlcBoUAhCF6oniyP13iDtu85ZsHwqw8P -nMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8SoXbYyy5AqdZ8CQpfvuriO9UNfgV1 -l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJeb4xKHFS04xC2AueE2RTmk6tJcL8T -EBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2pWHzsb+Fr4Wj0DChLoxlXxaBcJ2o -zogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4naU91WFGWlw0+0JpiMIl5OnMbpak -/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO1QIDAQABAoIBAHsTYKKoPPKVKOhO -SH5itPXLnytqSZjFDtB1L1T0XGGXIZ04lXmVHJ0xJ2klGq7VXM302H1Qx/JcyydZ -OkY/pXE3ChTGsPUHloybSrojFsnuso8ynqnAAzZzroDTuIuFQVADDP/woWTmPemG -VZYqfcsp5PgsWmyae8jM2ncLBdbRnSCtQ0FqMZPxHnazxoxh43fIisdZiHVretDR -cHR0MKig8ggetpRCPv8prHvYjllmnp+vFPNbI8DD6LUho0gtsKnGG+4k5NCUArrH -gfNK7ov/wsBe991XgC+tG/Xa8GgQJjSXr6qxwI6jPJ9d8EDHi31pqOWggJz0iPUL -U/5uNoECgYEA9U+ykujylIn7WPDcoNuXXk+xb5J+7p0En1dnFPDWEqM21rHfYHpo -X8ikfW63PSYp/472BLhum3TnjbgBQy8Uu80uGXWKCTCmnguMZVAw0yJn4bwCKmUM -hI8eaB231O3pz0AeoKfjj3Y25c6EF+P0wixO95qNj3gZs8fRE1o2BoUCgYEAz1EE -AHHqNus/2iGVS/OD0cGcZBzBgz790o1YHvaAZWTB8J4Q55Z8NnbxHKPk+1FRik2c -3iqNhDtfee/9j9QkNookjP6JqliKLj6AHAzqmKAviMN/akQHO9vYrKVrgGXts9X4 -k5Rn3Wyb1R9xSg9timalZ6HXsG0fqMKlQimroBECgYEAvfCPnCCyc0DDVQJqUkK0 -2VlrUbBkh+0b/yR1tEkl+BhYBbSD+PfOiRuaAWUQjFBYhH/1DYKgYc4K3Dh1Acga -ja842o2f66231PesJWYJ5/Dj7mzcYGcNYjZOnN8lz8DHIKnNDSfxkss9hoJ41Oa0 -RQR5wE3y/ftIK2O0Bv/6hOECgYEAhzh2fbvPYobKnQIumN16a0P3N60x4uMhN0YN -5hQt5vRzMvhGWmdjGTs3RND5t5pwrt60pIvoxgjb23LXVgqunAw6juQu8rbRWhxV -/vXFxksk9fVdk/hPJ5Hbn7A9j//rrNKxUcEIhfojIj1h7UZm0lDlLdT4FBD5B1/9 -3kvDr6ECgYEAu/OS+6ApNTH/LhSeeqw9jMtxJwu560WOzSsDk8LSlSDCZHwREwVU -M+uBWShYTDlPn6Bpe15dNHghvxFnZi12pS57l5AgD4J8AW4FEveDuIjj7BOexNFa -nB099pHr6MUM5s8zmw+9Z4Rc7gk8bwTyFAwMFf8ZYSyl4WIHQfTbhx0= ------END RSA PRIVATE KEY----- diff --git a/navicat-keygen/DESCipher.hpp b/navicat-keygen/DESCipher.hpp new file mode 100644 index 0000000..fe6b58c --- /dev/null +++ b/navicat-keygen/DESCipher.hpp @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include + +class DESCipher { +private: + DES_cblock _Key; + DES_key_schedule _Schedule; +public: + + DESCipher() noexcept : _Key{}, _Schedule{} {} + + void SetKey(const void* pKey) noexcept { + memcpy(&_Key, pKey, sizeof(_Key)); + DES_set_odd_parity(&_Key); + DES_set_key(&_Key, &_Schedule); + } + + void Clear() noexcept { + OPENSSL_cleanse(&_Key, sizeof(_Key)); + OPENSSL_cleanse(&_Schedule, sizeof(_Schedule)); + } + + void EncryptBlock(void* pBuffer) noexcept { + DES_cblock block; + DES_ecb_encrypt(reinterpret_cast(pBuffer), &block, &_Schedule, DES_ENCRYPT); + memcpy(pBuffer, &block, sizeof(block)); + } + + void DecryptBlock(void* pBuffer) noexcept { + DES_cblock block; + DES_ecb_encrypt(reinterpret_cast(pBuffer), &block, &_Schedule, DES_DECRYPT); + memcpy(pBuffer, &block, sizeof(block)); + } + + ~DESCipher() noexcept { + Clear(); + } +}; + diff --git a/navicat-keygen/Helper.cpp b/navicat-keygen/Helper.cpp new file mode 100644 index 0000000..92b38c0 --- /dev/null +++ b/navicat-keygen/Helper.cpp @@ -0,0 +1,82 @@ +#include "Helper.hpp" + +namespace Helper { + + // + // https://stackoverflow.com/questions/5288076/base64-encoding-and-decoding-with-openssl + // Thanks Omnifarious + // OpenSSL's base64 API is too dreadful to use + // + static const char b64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + static const char b64_reverse_table[128] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 + }; + + std::string base64_encode(const std::vector& bindata) { + using bytes = std::vector; + + if (bindata.size() > (std::numeric_limits::max() / 4u) * 3u) { + throw ::std::length_error("Converting too large a string to base64."); + } + + const std::size_t binlen = bindata.size(); + // Use = signs so the end is properly padded. + std::string retval((((binlen + 2) / 3) * 4), '='); + std::size_t outpos = 0; + int bits_collected = 0; + unsigned int accumulator = 0; + const bytes::const_iterator binend = bindata.end(); + + for (auto i = bindata.begin(); i != binend; ++i) { + accumulator = (accumulator << 8) | (*i & 0xffu); + bits_collected += 8; + while (bits_collected >= 6) { + bits_collected -= 6; + retval[outpos++] = b64_table[(accumulator >> bits_collected) & 0x3fu]; + } + } + if (bits_collected > 0) { // Any trailing bits that are missing. + assert(bits_collected < 6); + accumulator <<= 6 - bits_collected; + retval[outpos++] = b64_table[accumulator & 0x3fu]; + } + assert(outpos >= (retval.size() - 2)); + assert(outpos <= retval.size()); + return retval; + } + + std::vector base64_decode(const std::string& ascdata) { + std::vector retval; + const std::string::const_iterator last = ascdata.end(); + int bits_collected = 0; + unsigned int accumulator = 0; + + for (std::string::const_iterator i = ascdata.begin(); i != last; ++i) { + const int c = *i; + if (::std::isspace(c) || c == '=') { + // Skip whitespace and padding. Be liberal in what you accept. + continue; + } + if ((c > 127) || (c < 0) || (b64_reverse_table[c] > 63)) { + throw ::std::invalid_argument("This contains characters not legal in a base64 encoded string."); + } + accumulator = (accumulator << 6) | b64_reverse_table[c]; + bits_collected += 6; + if (bits_collected >= 8) { + bits_collected -= 8; + retval.emplace_back(static_cast((accumulator >> bits_collected) & 0xffu)); + } + } + return retval; + } + +} + diff --git a/navicat-keygen/Helper.hpp b/navicat-keygen/Helper.hpp new file mode 100644 index 0000000..ab11f85 --- /dev/null +++ b/navicat-keygen/Helper.hpp @@ -0,0 +1,45 @@ +#include +#include +#include + +namespace Helper { + template + bool ReadNumber(int& num, const char* msg, const char* err_msg) { + int temp; + std::string input; + while (true) { + std::cout << msg; + if (!std::getline(std::cin, input)) + return false; + + try { + temp = std::stoi(input, nullptr, 0); + if (min_num <= temp && temp <= max_num) { + num = temp; + return true; + } else { + throw std::invalid_argument("Invalid number"); + } + } catch (...) { + std::cout << err_msg << std::endl; + } + } + } + + template + struct ResourceGuard { + _Type* ptr; + + explicit ResourceGuard(_Type* p) noexcept : ptr(p) {} + + ~ResourceGuard() { + if (ptr) { + delete ptr; + ptr = nullptr; + } + } + }; + + std::string base64_encode(const std::vector& bindata); + std::vector base64_decode(const std::string& ascdata); +} diff --git a/navicat-keygen/Makefile b/navicat-keygen/Makefile deleted file mode 100644 index 28f4e7c..0000000 --- a/navicat-keygen/Makefile +++ /dev/null @@ -1,9 +0,0 @@ - -release : main.cpp - g++ -std=c++11 main.cpp -lcrypto -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -o navicat-keygen - -debug : main.cpp - g++ -std=c++11 -D_DEBUG main.cpp -lcrypto -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -o navicat-keygen - -clean: - rm navicat-keygen \ No newline at end of file diff --git a/navicat-keygen/NavicatKeygen.hpp b/navicat-keygen/NavicatKeygen.hpp new file mode 100644 index 0000000..0717955 --- /dev/null +++ b/navicat-keygen/NavicatKeygen.hpp @@ -0,0 +1,195 @@ +#pragma once +#include +#include +#include "DESCipher.hpp" + +class NavicatKeygen { +public: + enum class Language { + English, + SimplifiedChinese, + TraditionalChinese, + Japanese, + Polish, + Spanish, + French, + German, + Korean, + Russian, + Portuguese + }; + + enum class Product { + DataModeler, + Premium, + MySQL, + PostgreSQL, + Oracle, + SQLServer, + SQLite, + MariaDB, + MongoDB, + ReportViewer + }; +private: + std::random_device rand_dev; + std::default_random_engine rand_eng; + std::uniform_int_distribution rand; + uint8_t data[10]; +public: + + NavicatKeygen() : rand_eng(rand_dev()), rand(0, UINT8_MAX), data{} { + data[0] = 0x68; + data[1] = 0x2A; + } + + void SetLanguageSignature(Language _language) { + switch (_language) { + case Language::English: + data[5] = 0xAC; // Must be 0xAC for English version. + data[6] = 0x88; // Must be 0x88 for English version. + break; + case Language::SimplifiedChinese: + data[5] = 0xCE; // Must be 0xCE for Simplified Chinese version. + data[6] = 0x32; // Must be 0x32 for Simplified Chinese version. + break; + case Language::TraditionalChinese: + data[5] = 0xAA; // Must be 0xAA for Traditional Chinese version. + data[6] = 0x99; // Must be 0x99 for Traditional Chinese version. + break; + case Language::Japanese: + data[5] = 0xAD; // Must be 0xAD for Japanese version. Discoverer: @dragonflylee + data[6] = 0x82; // Must be 0x82 for Japanese version. Discoverer: @dragonflylee + break; + case Language::Polish: + data[5] = 0xBB; // Must be 0xBB for Polish version. Discoverer: @dragonflylee + data[6] = 0x55; // Must be 0x55 for Polish version. Discoverer: @dragonflylee + break; + case Language::Spanish: + data[5] = 0xAE; // Must be 0xAE for Spanish version. Discoverer: @dragonflylee + data[6] = 0x10; // Must be 0x10 for Spanish version. Discoverer: @dragonflylee + break; + case Language::French: + data[5] = 0xFA; // Must be 0xFA for French version. Discoverer: @Deltafox79 + data[6] = 0x20; // Must be 0x20 for French version. Discoverer: @Deltafox79 + break; + case Language::German: + data[5] = 0xB1; // Must be 0xB1 for German version. Discoverer: @dragonflylee + data[6] = 0x60; // Must be 0x60 for German version. Discoverer: @dragonflylee + break; + case Language::Korean: + data[5] = 0xB5; // Must be 0xB5 for Korean version. Discoverer: @dragonflylee + data[6] = 0x60; // Must be 0x60 for Korean version. Discoverer: @dragonflylee + break; + case Language::Russian: + data[5] = 0xEE; // Must be 0xB5 for Russian version. Discoverer: @dragonflylee + data[6] = 0x16; // Must be 0x60 for Russian version. Discoverer: @dragonflylee + break; + case Language::Portuguese: + data[5] = 0xCD; // Must be 0xCD for Portuguese version. Discoverer: @dragonflylee + data[6] = 0x49; // Must be 0x49 for Portuguese version. Discoverer: @dragonflylee + break; + default: + break; + } + } + + void SetLanguageSignature(uint8_t value0, uint8_t value1) { + data[5] = value0; + data[6] = value1; + } + + void SetProductSignature(Product _product) { + switch (_product) { + case Product::DataModeler: + data[7] = 0x47; + break; + case Product::Premium: + data[7] = 0x65; + break; + case Product::MySQL: + data[7] = 0x68; + break; + case Product::PostgreSQL: + data[7] = 0x6C; + break; + case Product::Oracle: + data[7] = 0x70; + break; + case Product::SQLServer: + data[7] = 0x74; + break; + case Product::SQLite: + data[7] = 0x78; + break; + case Product::MariaDB: + data[7] = 0x7C; + break; + case Product::MongoDB: + data[7] = 0x80; + break; + case Product::ReportViewer: + data[7] = 0xb; + default: + break; + } + } + + void SetProductSignature(uint8_t value) { + data[7] = value; + } + + void SetVersion(uint8_t version) { + data[8] = version << 4; + } + + void Generate() { + static const_DES_cblock DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; + DESCipher cipher; + + data[2] = static_cast(rand(rand_eng)); + data[3] = static_cast(rand(rand_eng)); + data[4] = static_cast(rand(rand_eng)); + data[9] = 0x32; + + cipher.SetKey(&DESKey); + cipher.EncryptBlock(data + 2); + } + + std::string GetKey() const { + static const char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; + std::string Key; + + Key.resize(16); + + Key[0] = EncodeTable[data[0] >> 3]; + Key[1] = EncodeTable[(data[0] & 0x07) << 2 | data[1] >> 6]; + Key[2] = EncodeTable[data[1] >> 1 & 0x1F]; + Key[3] = EncodeTable[(data[1] & 0x1) << 4 | data[2] >> 4]; + Key[4] = EncodeTable[(data[2] & 0xF) << 1 | data[3] >> 7]; + Key[5] = EncodeTable[data[3] >> 2 & 0x1F]; + Key[6] = EncodeTable[(data[3] << 3 & 0x1F) | data[4] >> 5]; + Key[7] = EncodeTable[data[4] & 0x1F]; + + Key[8] = EncodeTable[data[5] >> 3]; + Key[9] = EncodeTable[(data[5] & 0x07) << 2 | data[6] >> 6]; + Key[10] = EncodeTable[data[6] >> 1 & 0x1F]; + Key[11] = EncodeTable[(data[6] & 0x1) << 4 | data[7] >> 4]; + Key[12] = EncodeTable[(data[7] & 0xF) << 1 | data[8] >> 7]; + Key[13] = EncodeTable[data[8] >> 2 & 0x1F]; + Key[14] = EncodeTable[(data[8] << 3 & 0x1F) | data[9] >> 5]; + Key[15] = EncodeTable[data[9] & 0x1F]; + + return Key; + } + + std::string GetFormatedKey() const { + std::string Key = GetKey(); + Key.insert(Key.begin() + 4, '-'); + Key.insert(Key.begin() + 5 + 4, '-'); + Key.insert(Key.begin() + 10 + 4, '-'); + return Key; + } + +}; + diff --git a/navicat-keygen/RSACipher.hpp b/navicat-keygen/RSACipher.hpp new file mode 100644 index 0000000..5019022 --- /dev/null +++ b/navicat-keygen/RSACipher.hpp @@ -0,0 +1,280 @@ +#pragma once +#include +#include +#include +#include +#include + +class OpenSSLException { +private: + const unsigned long ErrorCode; +public: + explicit OpenSSLException(unsigned long code) : ErrorCode(code) {} + unsigned long GetErrorCode() const noexcept { return ErrorCode; } + const char* GetErrorString() const noexcept { return ERR_error_string(ErrorCode, nullptr); } +}; + +template +class OpenSSLObject { +protected: + _Type* _pObj; +public: + // take over the object passed in + explicit OpenSSLObject(_Type* pObj) noexcept : _pObj(pObj) {} + + OpenSSLObject(const OpenSSLObject<_Type, _Type_free>& other) = delete; + + OpenSSLObject(OpenSSLObject<_Type, _Type_free>&& other) noexcept : _pObj(other._pObj) { + other._pObj = nullptr; + } + + OpenSSLObject<_Type, _Type_free>& + operator=(const OpenSSLObject<_Type, _Type_free>& other) = delete; + + OpenSSLObject<_Type, _Type_free>& + operator=(_Type* other) noexcept { + if (_pObj != other) { + _Type_free(_pObj); + } + _pObj = other; + return *this; + } + + OpenSSLObject<_Type, _Type_free>& + operator=(OpenSSLObject<_Type, _Type_free>&& other) noexcept { + if (&other != this) { + if (_pObj) + _Type_free(_pObj); + _pObj = other._pObj; + other._pObj = nullptr; + } + return *this; + } + + _Type* GetPointer() const noexcept { + return _pObj; + } + + ~OpenSSLObject() { + if (_pObj) { + _Type_free(_pObj); + _pObj = nullptr; + } + } +}; + +class RSACipher { +private: + OpenSSLObject _RsaObj; + + explicit RSACipher(RSA* lpRsa) : _RsaObj(lpRsa) {} +public: + + RSACipher(const RSACipher&) = delete; + RSACipher(RSACipher&&) = delete; + RSACipher& operator=(const RSACipher&) = delete; + RSACipher& operator=(RSACipher&&) = delete; + + enum class KeyType { + PrivateKey, + PublicKey + }; + + enum class KeyFormat { + NotSpecified, + PEM, + PKCS1 + }; + + static RSACipher* Create() { + RSA* pObj = RSA_new(); + return pObj ? new RSACipher(pObj) : nullptr; + } + + bool GenerateKey(int bits, unsigned int e = RSA_F4) { + bool bSuccess = false; + OpenSSLObject bn_e(BN_new()); + + if (!bn_e.GetPointer()) + return false; + + if (!BN_set_word(bn_e.GetPointer(), e)) + return false; + + if (!RSA_generate_key_ex(_RsaObj.GetPointer(), bits, bn_e.GetPointer(), nullptr)) + return false; + + return true; + } + + template + bool ExportKeyToFile(const std::string& filename) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_file(BIO_new_file(filename.c_str(), "w")); + + if (bio_file.GetPointer() == nullptr) + return false; + + if (_Type == KeyType::PrivateKey) { + bSuccess = PEM_write_bio_RSAPrivateKey(bio_file.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr) != 0; + } else { + if (_Format == KeyFormat::PEM) + bSuccess = PEM_write_bio_RSA_PUBKEY(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0; + if (_Format == KeyFormat::PKCS1) + bSuccess = PEM_write_bio_RSAPublicKey(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0; + } + + return bSuccess; + } + + template + std::string ExportKeyString() { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + std::string KeyString; + OpenSSLObject bio_mem(BIO_new(BIO_s_mem())); + long len = 0; + const char* lpdata = nullptr; + + if (bio_mem.GetPointer() == nullptr) + return KeyString; + + if (_Type == KeyType::PrivateKey) { + if (!PEM_write_bio_RSAPrivateKey(bio_mem.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr)) + return KeyString; + } else { + if (_Format == KeyFormat::PEM) { + if (!PEM_write_bio_RSA_PUBKEY(bio_mem.GetPointer(), _RsaObj.GetPointer())) + return KeyString; + } + if (_Format == KeyFormat::PKCS1) { + if (!PEM_write_bio_RSAPublicKey(bio_mem.GetPointer(), _RsaObj.GetPointer())) + return KeyString; + } + } + + len = BIO_get_mem_data(bio_mem.GetPointer(), &lpdata); + KeyString.assign(lpdata, static_cast(len)); + return KeyString; + } + + template + bool ImportKeyFromFile(const std::string& filename) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_file(BIO_new_file(filename.c_str(), "r")); + OpenSSLObject newRsaObj(nullptr); + + if (bio_file.GetPointer() == nullptr) + return false; + + if (_Type == KeyType::PrivateKey) { + newRsaObj = PEM_read_bio_RSAPrivateKey(bio_file.GetPointer(), nullptr, nullptr, nullptr); + } else { + if (_Format == KeyFormat::PEM) + newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_file.GetPointer(), nullptr, nullptr, nullptr); + if (_Format == KeyFormat::PKCS1) + newRsaObj = PEM_read_bio_RSAPublicKey(bio_file.GetPointer(), nullptr, nullptr, nullptr); + } + + if (newRsaObj.GetPointer()) { + _RsaObj = static_cast&&>(newRsaObj); + bSuccess = true; + } + + return bSuccess; + } + + template + bool ImportKeyString(const std::string& KeyString) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_mem(BIO_new(BIO_s_mem())); + OpenSSLObject newRsaObj(nullptr); + + if (bio_mem.GetPointer() == nullptr) + return false; + + BIO_puts(bio_mem.GetPointer(), KeyString.c_str()); + + if (_Type == KeyType::PrivateKey) { + newRsaObj = PEM_read_bio_RSAPrivateKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + } else { + if (_Format == KeyFormat::PEM) + newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + if (_Format == KeyFormat::PKCS1) + newRsaObj = PEM_read_bio_RSAPublicKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + } + + if (newRsaObj.GetPointer()) { + _RsaObj = static_cast&&>(newRsaObj); + bSuccess = true; + } + + return bSuccess; + } + + template + int Encrypt(const void* from, int len, void* to, int padding) { + int write_bytes = 0; + + if (_Type == KeyType::PrivateKey) { + write_bytes = RSA_private_encrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } else { + write_bytes = RSA_public_encrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } + + if (write_bytes == -1) + write_bytes = 0; + return write_bytes; + } + + template + int Decrypt(const void* from, int len, void* to, int padding) { + int write_bytes = 0; + + if (_Type == KeyType::PrivateKey) { + write_bytes = RSA_private_decrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } else { + write_bytes = RSA_public_decrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } + + if (write_bytes == -1) + write_bytes = 0; + return write_bytes; + } + +}; + diff --git a/navicat-keygen/main.cpp b/navicat-keygen/main.cpp index 1c088bb..ae21b97 100644 --- a/navicat-keygen/main.cpp +++ b/navicat-keygen/main.cpp @@ -1,282 +1,217 @@ -#include -#include #include -#include -#include +#include "Helper.hpp" +#include "RSACipher.hpp" +#include "DESCipher.hpp" +#include "NavicatKeygen.hpp" -#include -#include -#include -#include -#include -#include +#include +#include +#include -enum NavicatLanguage { - English, - SimplifiedChinese, - TraditionalChinese, - Japanese, - Polish, - Spanish, - French, - German, - Korean, - Russian, - Portuguese -}; - -void GenerateSnKey(char (&SnKey)[16], NavicatLanguage _language) { - static char EncodeTable[] = "ABCDEFGH8JKLMN9PQRSTUVWXYZ234567"; // Thanks for discoveries from @Wizr. - // This is not a standard Base32 alphabet table. - // The differences are: - // | Standard | Non-standard | - // |------------|----------------| - // | 'I' | '8' | - // | 'O' | '9' | - - static DES_cblock DESKey = { 0x64, 0xAD, 0xF3, 0x2F, 0xAE, 0xF2, 0x1A, 0x27 }; - - unsigned char temp_snKey[10] = { 0x68, 0x2a }; // must start with 0x68, 0x2a - temp_snKey[2] = rand(); - temp_snKey[3] = rand(); - temp_snKey[4] = rand(); - - switch (_language) { - case English: - temp_snKey[5] = 0xAC; // Must be 0xAC for English version. - temp_snKey[6] = 0x88; // Must be 0x88 for English version. - break; - case SimplifiedChinese: - temp_snKey[5] = 0xCE; // Must be 0xCE for Simplified Chinese version. - temp_snKey[6] = 0x32; // Must be 0x32 for Simplified Chinese version. - break; - case TraditionalChinese: - temp_snKey[5] = 0xAA; // Must be 0xAA for Traditional Chinese version. - temp_snKey[6] = 0x99; // Must be 0x99 for Traditional Chinese version. - break; - case Japanese: - temp_snKey[5] = 0xAD; // Must be 0xAD for Japanese version. Discoverer: @dragonflylee - temp_snKey[6] = 0x82; // Must be 0x82 for Japanese version. Discoverer: @dragonflylee - break; - case Polish: - temp_snKey[5] = 0xBB; // Must be 0xBB for Polish version. Discoverer: @dragonflylee - temp_snKey[6] = 0x55; // Must be 0x55 for Polish version. Discoverer: @dragonflylee - break; - case Spanish: - temp_snKey[5] = 0xAE; // Must be 0xAE for Spanish version. Discoverer: @dragonflylee - temp_snKey[6] = 0x10; // Must be 0x10 for Spanish version. Discoverer: @dragonflylee - break; - case French: - temp_snKey[5] = 0xFA; // Must be 0xFA for French version. Discoverer: @Deltafox79 - temp_snKey[6] = 0x20; // Must be 0x20 for French version. Discoverer: @Deltafox79 - break; - case German: - temp_snKey[5] = 0xB1; // Must be 0xB1 for German version. Discoverer: @dragonflylee - temp_snKey[6] = 0x60; // Must be 0x60 for German version. Discoverer: @dragonflylee - break; - case Korean: - temp_snKey[5] = 0xB5; // Must be 0xB5 for Korean version. Discoverer: @dragonflylee - temp_snKey[6] = 0x60; // Must be 0x60 for Korean version. Discoverer: @dragonflylee - break; - case Russian: - temp_snKey[5] = 0xEE; // Must be 0xB5 for Russian version. Discoverer: @dragonflylee - temp_snKey[6] = 0x16; // Must be 0x60 for Russian version. Discoverer: @dragonflylee - break; - case Portuguese: - temp_snKey[5] = 0xCD; // Must be 0xCD for Portuguese version. Discoverer: @dragonflylee - temp_snKey[6] = 0x49; // Must be 0x49 for Portuguese version. Discoverer: @dragonflylee - break; - default: - break; - } - - temp_snKey[7] = 0x65; // 0x65 - commercial, 0x66 - non-commercial - temp_snKey[8] = 0xC0; // High 4-bits = version number. Low 4-bits doesn't know, but can be used to delay activation time. - temp_snKey[9] = 0x32; // 0xFB is Not-For-Resale-30-days license. - // 0xFC is Not-For-Resale-90-days license. - // 0xFD is Not-For-Resale-365-days license. - // 0xFE is Not-For-Resale license. - // 0xFF is Site license. - // Must not be 0x00. 0x01-0xFA is ok. - - DES_key_schedule schedule; - DES_set_odd_parity(&DESKey); - DES_set_key(&DESKey, &schedule); - DES_cblock enc_temp_snKey; - - DES_ecb_encrypt(reinterpret_cast(temp_snKey + 2), &enc_temp_snKey, &schedule, DES_ENCRYPT); - memmove(temp_snKey + 2, enc_temp_snKey, sizeof(enc_temp_snKey)); - - SnKey[0] = EncodeTable[temp_snKey[0] >> 3]; - SnKey[1] = EncodeTable[(temp_snKey[0] & 0x07) << 2 | temp_snKey[1] >> 6]; - SnKey[2] = EncodeTable[temp_snKey[1] >> 1 & 0x1F]; - SnKey[3] = EncodeTable[(temp_snKey[1] & 0x1) << 4 | temp_snKey[2] >> 4]; - SnKey[4] = EncodeTable[(temp_snKey[2] & 0xF) << 1 | temp_snKey[3] >> 7]; - SnKey[5] = EncodeTable[temp_snKey[3] >> 2 & 0x1F]; - SnKey[6] = EncodeTable[temp_snKey[3] << 3 & 0x1F | temp_snKey[4] >> 5]; - SnKey[7] = EncodeTable[temp_snKey[4] & 0x1F]; - - SnKey[8] = EncodeTable[temp_snKey[5] >> 3]; - SnKey[9] = EncodeTable[(temp_snKey[5] & 0x07) << 2 | temp_snKey[6] >> 6]; - SnKey[10] = EncodeTable[temp_snKey[6] >> 1 & 0x1F]; - SnKey[11] = EncodeTable[(temp_snKey[6] & 0x1) << 4 | temp_snKey[7] >> 4]; - SnKey[12] = EncodeTable[(temp_snKey[7] & 0xF) << 1 | temp_snKey[8] >> 7]; - SnKey[13] = EncodeTable[temp_snKey[8] >> 2 & 0x1F]; - SnKey[14] = EncodeTable[temp_snKey[8] << 3 & 0x1F | temp_snKey[9] >> 5]; - SnKey[15] = EncodeTable[temp_snKey[9] & 0x1F]; - - char formated_SnKeyString[20] = { }; - snprintf(formated_SnKeyString, sizeof(formated_SnKeyString), "%.4s-%.4s-%.4s-%.4s", SnKey, SnKey + 4, SnKey + 8, SnKey + 12); - - std::cout << std::endl; - std::cout - << "SnKey:" << std::endl - << formated_SnKeyString << std::endl - << std::endl; +void help() { + std::cout << "Usage:" << std::endl + << " ./navicat-keygen " << std::endl + << std::endl; } -int main(int argc, char* argv[]) { +int main(int argc, char* argv[], char* envp[]) { if (argc != 2) { - std::cout - << "Usage:" << std::endl - << " ./navicat-keygen " << std::endl - << std::endl; + help(); return 0; } - srand(time(0)); - + Helper::ResourceGuard cipher(RSACipher::Create()); + NavicatKeygen keygen; + std::string username; + std::string organization; + + std::string RequestCode_b64; + std::vector RequestCode; + char RequestInfo[256] = {}; + char ResponseInfo[256] = {}; + std::vector ResponseCode; + std::string ResponseCode_b64; + + rapidjson::Document json; + rapidjson::Value N_Key; + rapidjson::Value N_Value; + rapidjson::Value O_Key; + rapidjson::Value O_Value; + rapidjson::Value T_Key; + rapidjson::Value T_Value; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + + if (cipher.ptr == nullptr) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Failed to create RSACipher." << std::endl; + return 0; + } + + if (!cipher.ptr->ImportKeyFromFile(argv[1])) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Failed to load RSA-2048 key." << std::endl; + return 0; + } + + keygen.SetProductSignature(NavicatKeygen::Product::Premium); + std::cout - << "Which is your Navicat language?" << std::endl - << "0. English" << std::endl - << "1. Simplified Chinese" << std::endl - << "2. Traditional Chinese" << std::endl - << "3. Japanese" << std::endl - << "4. Polish" << std::endl - << "5. Spanish" << std::endl - << "6. French" << std::endl - << "7. German" << std::endl - << "8. Korean" << std::endl - << "9. Russian" << std::endl - << "10. Portuguese" << std::endl - << std::endl; - - int LanguageIndex = -1; - while(true) { - std::cout << "(input index)>"; - - std::string temp; - std::getline(std::cin, temp); - try { - LanguageIndex = std::stoi(temp); - if (LanguageIndex < 0 || LanguageIndex > 10) - throw std::invalid_argument("Invalid index"); - break; - } catch(...) { - std::cout << "Invalid index." << std::endl; - continue; - } - } - - char SnKey[16] = { }; - GenerateSnKey(SnKey, static_cast(LanguageIndex)); + << "Which is your Navicat Premium language?" << std::endl + << "0. English" << std::endl + << "1. Simplified Chinese" << std::endl + << "2. Traditional Chinese" << std::endl + << "3. Japanese" << std::endl + << "4. Polish" << std::endl + << "5. Spanish" << std::endl + << "6. French" << std::endl + << "7. German" << std::endl + << "8. Korean" << std::endl + << "9. Russian" << std::endl + << "10. Portuguese" << std::endl + << std::endl; - double current_time - = std::chrono::duration_cast>( - std::chrono::system_clock::now().time_since_epoch()).count(); + { + int num; + if (!Helper::ReadNumber<0, 10>(num, "(Input index)> ", "Invalid index.")) + return 0; + keygen.SetLanguageSignature(static_cast(num)); - BIO* BIO_file = BIO_new_file(argv[1], "r"); - if (BIO_file == nullptr) { - std::cout << "Failed to read file." << std::endl; - return -1; + if (!Helper::ReadNumber<0, 15>(num, "(Input major version number, range: 0 ~ 15, default: 12)> ", "Invalid number.")) + return 0; + keygen.SetVersion(static_cast(num)); } - RSA* PrivateKey = PEM_read_bio_RSAPrivateKey(BIO_file, nullptr, nullptr, nullptr); - if (PrivateKey == nullptr) { - std::cout << "Failed to load private key." << std::endl; - return -2; - } + // + // Generate snKey + // + keygen.Generate(); + std::cout << std::endl; + std::cout << "Serial number:" << std::endl; + std::cout << keygen.GetFormatedKey() << std::endl; + std::cout << std::endl; - BIO_free_all(BIO_file); - - std::string Name; - std::string Organization; - - std::cin.clear(); + // + // Get user name + // std::cout << "Your name: "; - std::getline(std::cin, Name); - std::cout << "Yout organization: "; - std::getline(std::cin, Organization); + if (!std::getline(std::cin, username)) + return 0; - std::string buffer; - std::cout << "Input Request Code (in Base64), empty line to return:" << std::endl; - while(true) { + // + // Get organization name + // + std::cout << "Your organization: "; + if (!std::getline(std::cin, organization)) + return 0; + + std::cout << std::endl; + + // + // Get request code in base64 + // + std::cout << "Input request code (in Base64), input empty line to end:" << std::endl; + while (true) { std::string temp; - std::getline(std::cin, temp); - buffer += temp; + if (!std::getline(std::cin, temp)) + return 0; + if (temp.empty()) break; + + RequestCode_b64 += temp; } - unsigned char enc_data[1024] = { }; - char data[1024] = { }; - - std::string DeviceIdentifier(""); - EVP_DecodeBlock(enc_data, reinterpret_cast(buffer.c_str()), buffer.length()); - if (RSA_private_decrypt(256, enc_data, reinterpret_cast(data), PrivateKey, RSA_PKCS1_PADDING) == -1) { - std::cout << "Failed to decrypt data." << std::endl; - return -3; - } -#ifdef _DEBUG - std::cout << "-----------Begin Request Code Data---------------" << std::endl; - std::cout << data << std::endl; - std::cout <<"-----------End Request Code Data---------------" << std::endl; -#endif - // Get DeviceIdentifier from data. - for (int i = 0, length = strlen(data) - 4; i < length; ++i) { - if(data[i] == '"' && - data[i + 1] == 'D' && - data[i + 2] == 'I' && - data[i + 3] == '"') { - - char temp[256] = { }; - int x = i + 4, j = 0; - while (data[x] != '"' && x < length) - x++; - x++; - while (data[x] != '"' && j < 256) { - temp[j++] = data[x]; - x++; - } - DeviceIdentifier += temp; - break; - } + // + // Get request code in raw bytes + // + try { + RequestCode = Helper::base64_decode(RequestCode_b64); + } catch(...) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Helper::base64_decode fails." << std::endl; + return 0; } - memset(data, 0, sizeof(data)); - memset(enc_data, 0, sizeof(enc_data)); + // + // Decrypt to get request info + // + if (!cipher.ptr->Decrypt(RequestCode.data(), static_cast(RequestCode.size()), RequestInfo, RSA_PKCS1_PADDING)) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Decrypt fails." << std::endl; + return 0; + } - snprintf(data, sizeof(data), "{\n \"DI\" : \"%s\", \n \"T\" : \"%lf\", \n \"K\" : \"%.16s\", \n \"N\" : \"%s\", \n \"O\" : \"%s\"\n}", - DeviceIdentifier.c_str(), - current_time, - SnKey, - Name.c_str(), - Organization.c_str() - ); + // + // print out request info + // + std::cout << "Request Info:" << std::endl; + std::cout << RequestInfo << std::endl; + std::cout << std::endl; -#ifdef _DEBUG - std::cout << "-----------Begin Activation Code Data---------------" << std::endl; - std::cout << data << std::endl; - std::cout << "-----------End Activation Code Data---------------" << std::endl; -#endif + // + // Generate response info + // + json.Parse(RequestInfo); + json.RemoveMember("P"); // remove Platform info - RSA_private_encrypt(strlen(data), reinterpret_cast(data), enc_data, PrivateKey, RSA_PKCS1_PADDING); + N_Key.SetString("N", 1); + N_Value.SetString(username.c_str(), static_cast(username.length())); + O_Key.SetString("O", 1); + O_Value.SetString(organization.c_str(), static_cast(organization.length())); + T_Key.SetString("T", 1); + T_Value.SetUint(static_cast(std::time(nullptr))); - char result[1024] = { }; - EVP_EncodeBlock(reinterpret_cast(result), enc_data, 256); - std::cout << "Activation Code:" << std::endl; - std::cout << result << std::endl; + json.AddMember(N_Key, N_Value, json.GetAllocator()); + json.AddMember(O_Key, O_Value, json.GetAllocator()); + json.AddMember(T_Key, T_Value, json.GetAllocator()); + + json.Accept(writer); + + if (buffer.GetSize() > 240) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Response info is too long." << std::endl; + return 0; + } + + // + // print out response info + // + memcpy(ResponseInfo, buffer.GetString(), buffer.GetSize()); + std::cout << "Response Info:" << std::endl; + std::cout << ResponseInfo << std::endl; + std::cout << std::endl; + + // + // encrypt response info + // + ResponseCode.resize(256); + if (!cipher.ptr->Encrypt(ResponseInfo, + static_cast(strlen(ResponseInfo)), + ResponseCode.data(), + RSA_PKCS1_PADDING)) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Encrypt fails." << std::endl; + return 0; + } + + // + // Encode encrypted response info in base64 format + // + try { + ResponseCode_b64 = Helper::base64_encode(ResponseCode); + } catch(...) { + std::cout << "@Function: " << __FUNCTION__ << " LINE: " << __LINE__ << std::endl; + std::cout << "ERROR: Helper::base64_encode fails." << std::endl; + return 0; + } + + // + // print out activation code + // + std::cout << "License:" << std::endl; + std::cout << ResponseCode_b64 << std::endl; - RSA_free(PrivateKey); return 0; } + diff --git a/navicat-patcher/FileMapper.hpp b/navicat-patcher/FileMapper.hpp new file mode 100644 index 0000000..b13cf6a --- /dev/null +++ b/navicat-patcher/FileMapper.hpp @@ -0,0 +1,78 @@ +#pragma once +#include +#include +#include +#include +#include + +class FileMapper { +private: + int _fd; + size_t _MapSize; + void* _pView; +public: + + class AlreadyInUseException {}; + + FileMapper() noexcept : + _fd(-1), + _MapSize(static_cast(-1)), + _pView(reinterpret_cast(-1)) { } + + void Unmap() noexcept { + if (_pView != reinterpret_cast(-1)) { + munmap(_pView, _MapSize); + _MapSize = static_cast(-1); + _pView = reinterpret_cast(-1); + } + } + + void Close() noexcept { + if (_fd != -1) { + close(_fd); + _fd = -1; + } + } + + bool OpenFile(const char* FilePath) { + if (_fd != -1) + throw AlreadyInUseException(); + _fd = open(FilePath, O_RDWR, S_IRUSR | S_IWUSR); + return _fd != -1; + } + + bool GetFileSize(off_t& refSize) noexcept { + struct stat fd_stat = {}; + if (fstat(_fd, &fd_stat) != 0) { + return false; + } else { + refSize = fd_stat.st_size; + return true; + } + } + + bool Map(size_t Size) { + if (_pView != reinterpret_cast(-1)) + throw AlreadyInUseException(); + + _pView = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0); + + if (_pView != reinterpret_cast(-1)) { + _MapSize = Size; + return true; + } else { + return false; + } + } + + template + _Type* GetView() const noexcept { + return reinterpret_cast<_Type*>(_pView); + } + + ~FileMapper() { + Unmap(); + Close(); + } +}; + diff --git a/navicat-patcher/Helper.cpp b/navicat-patcher/Helper.cpp new file mode 100644 index 0000000..3158161 --- /dev/null +++ b/navicat-patcher/Helper.cpp @@ -0,0 +1,97 @@ +#include "Helper.hpp" +#include +#include +#include +#include +#include + +namespace Helper { + + static jmp_buf env; + + static void SIGSEGV_Handler(int sig) { + siglongjmp(env, 1); + } + + // + // read byte(s) at address `p` as _Type to `out` + // succeed if return true, otherwise return false + // + template + static inline bool ProbeForRead(const void* p, void* out) { + int r = sigsetjmp(env, 1); + if (r == 0) { + *reinterpret_cast<_Type*>(out) = *reinterpret_cast(p); + return true; + } else { + return false; + } + } + + // + // Print memory data in [from, to) at least + // If `base` is not nullptr, print address as offset. Otherwise, as absolute address. + // NOTICE: + // `base` must >= `from` + // + void PrintMemory(const void* from, const void* to, const void* base) { + const uint8_t* start = reinterpret_cast(from); + const uint8_t* end = reinterpret_cast(to); + const uint8_t* base_ptr = reinterpret_cast(base); + + if (start >= end) + return; + + while (reinterpret_cast(start) % 16) + start--; + + while (reinterpret_cast(start) % 16) + end++; + + void (*prev_handler)(int) = signal(SIGSEGV, SIGSEGV_Handler); + while (start < end) { + uint16_t value[16] = {}; + + if (base_ptr) + printf("+0x%p ", reinterpret_cast(start - base_ptr)); + else + printf("0x%p ", start); + + for (int i = 0; i < 16; ++i) { + if (ProbeForRead(start + i, value + i)) { + printf("%02x ", value[i]); + } else { + value[i] = 0xffff; + printf("?? "); + } + } + + printf(" "); + + for (int i = 0; i < 16; ++i) { + if (value[i] < 0x20) { + printf("."); + } else if (value[i] > 0x7e) { + printf("."); + } else { + printf("%c", value[i]); + } + } + printf("\n"); + start += 0x10; + } + signal(SIGSEGV, prev_handler); + } + + void ErrorReport(const char* at, size_t line, const char* msg) { + printf("ERROR: @%s LINE: %zu\n", at, line); + printf("%s\n", msg); + } + + void ErrorReport(const char* at, size_t line, const char* msg, int err_code) { + printf("ERROR: @%s LINE: %zu\n", at, line); + printf("%s CODE: 0x%08x\n", msg, err_code); + printf("REASON: %s\n", strerror(err_code)); + } +} + diff --git a/navicat-patcher/Helper.hpp b/navicat-patcher/Helper.hpp new file mode 100644 index 0000000..3cc5cef --- /dev/null +++ b/navicat-patcher/Helper.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +namespace Helper { + + // + // Print memory data in [from, to) at least + // If `base` is not nullptr, print address as offset. Otherwise, as absolute address. + // NOTICE: + // `base` must >= `from` + // + void PrintMemory(const void* from, const void* to, const void* base); + + void ErrorReport(const char* at, size_t line, const char* msg); + void ErrorReport(const char* at, size_t line, const char* msg, int err_code); + + template + struct ResourceGuard { + _Type* ptr; + + explicit ResourceGuard(_Type* p) noexcept : ptr(p) {} + + ~ResourceGuard() { + if (ptr) { + delete ptr; + ptr = nullptr; + } + } + }; +} + +#define REPORT_ERROR(msg) Helper::ErrorReport(__FUNCTION__, __LINE__, msg) +#define REPORT_ERROR_WITH_CODE(msg) Helper::ErrorReport(__FUNCTION__, __LINE__, msg, errno) +#define PRINT_MESSAGE(msg) puts(msg) diff --git a/navicat-patcher/Makefile b/navicat-patcher/Makefile deleted file mode 100644 index 0c08a89..0000000 --- a/navicat-patcher/Makefile +++ /dev/null @@ -1,6 +0,0 @@ - -release : main.c - gcc main.c -o navicat-patcher - -clean: - rm navicat-patcher \ No newline at end of file diff --git a/navicat-patcher/RSACipher.hpp b/navicat-patcher/RSACipher.hpp new file mode 100644 index 0000000..5019022 --- /dev/null +++ b/navicat-patcher/RSACipher.hpp @@ -0,0 +1,280 @@ +#pragma once +#include +#include +#include +#include +#include + +class OpenSSLException { +private: + const unsigned long ErrorCode; +public: + explicit OpenSSLException(unsigned long code) : ErrorCode(code) {} + unsigned long GetErrorCode() const noexcept { return ErrorCode; } + const char* GetErrorString() const noexcept { return ERR_error_string(ErrorCode, nullptr); } +}; + +template +class OpenSSLObject { +protected: + _Type* _pObj; +public: + // take over the object passed in + explicit OpenSSLObject(_Type* pObj) noexcept : _pObj(pObj) {} + + OpenSSLObject(const OpenSSLObject<_Type, _Type_free>& other) = delete; + + OpenSSLObject(OpenSSLObject<_Type, _Type_free>&& other) noexcept : _pObj(other._pObj) { + other._pObj = nullptr; + } + + OpenSSLObject<_Type, _Type_free>& + operator=(const OpenSSLObject<_Type, _Type_free>& other) = delete; + + OpenSSLObject<_Type, _Type_free>& + operator=(_Type* other) noexcept { + if (_pObj != other) { + _Type_free(_pObj); + } + _pObj = other; + return *this; + } + + OpenSSLObject<_Type, _Type_free>& + operator=(OpenSSLObject<_Type, _Type_free>&& other) noexcept { + if (&other != this) { + if (_pObj) + _Type_free(_pObj); + _pObj = other._pObj; + other._pObj = nullptr; + } + return *this; + } + + _Type* GetPointer() const noexcept { + return _pObj; + } + + ~OpenSSLObject() { + if (_pObj) { + _Type_free(_pObj); + _pObj = nullptr; + } + } +}; + +class RSACipher { +private: + OpenSSLObject _RsaObj; + + explicit RSACipher(RSA* lpRsa) : _RsaObj(lpRsa) {} +public: + + RSACipher(const RSACipher&) = delete; + RSACipher(RSACipher&&) = delete; + RSACipher& operator=(const RSACipher&) = delete; + RSACipher& operator=(RSACipher&&) = delete; + + enum class KeyType { + PrivateKey, + PublicKey + }; + + enum class KeyFormat { + NotSpecified, + PEM, + PKCS1 + }; + + static RSACipher* Create() { + RSA* pObj = RSA_new(); + return pObj ? new RSACipher(pObj) : nullptr; + } + + bool GenerateKey(int bits, unsigned int e = RSA_F4) { + bool bSuccess = false; + OpenSSLObject bn_e(BN_new()); + + if (!bn_e.GetPointer()) + return false; + + if (!BN_set_word(bn_e.GetPointer(), e)) + return false; + + if (!RSA_generate_key_ex(_RsaObj.GetPointer(), bits, bn_e.GetPointer(), nullptr)) + return false; + + return true; + } + + template + bool ExportKeyToFile(const std::string& filename) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_file(BIO_new_file(filename.c_str(), "w")); + + if (bio_file.GetPointer() == nullptr) + return false; + + if (_Type == KeyType::PrivateKey) { + bSuccess = PEM_write_bio_RSAPrivateKey(bio_file.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr) != 0; + } else { + if (_Format == KeyFormat::PEM) + bSuccess = PEM_write_bio_RSA_PUBKEY(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0; + if (_Format == KeyFormat::PKCS1) + bSuccess = PEM_write_bio_RSAPublicKey(bio_file.GetPointer(), _RsaObj.GetPointer()) != 0; + } + + return bSuccess; + } + + template + std::string ExportKeyString() { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + std::string KeyString; + OpenSSLObject bio_mem(BIO_new(BIO_s_mem())); + long len = 0; + const char* lpdata = nullptr; + + if (bio_mem.GetPointer() == nullptr) + return KeyString; + + if (_Type == KeyType::PrivateKey) { + if (!PEM_write_bio_RSAPrivateKey(bio_mem.GetPointer(), _RsaObj.GetPointer(), nullptr, nullptr, 0, nullptr, nullptr)) + return KeyString; + } else { + if (_Format == KeyFormat::PEM) { + if (!PEM_write_bio_RSA_PUBKEY(bio_mem.GetPointer(), _RsaObj.GetPointer())) + return KeyString; + } + if (_Format == KeyFormat::PKCS1) { + if (!PEM_write_bio_RSAPublicKey(bio_mem.GetPointer(), _RsaObj.GetPointer())) + return KeyString; + } + } + + len = BIO_get_mem_data(bio_mem.GetPointer(), &lpdata); + KeyString.assign(lpdata, static_cast(len)); + return KeyString; + } + + template + bool ImportKeyFromFile(const std::string& filename) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_file(BIO_new_file(filename.c_str(), "r")); + OpenSSLObject newRsaObj(nullptr); + + if (bio_file.GetPointer() == nullptr) + return false; + + if (_Type == KeyType::PrivateKey) { + newRsaObj = PEM_read_bio_RSAPrivateKey(bio_file.GetPointer(), nullptr, nullptr, nullptr); + } else { + if (_Format == KeyFormat::PEM) + newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_file.GetPointer(), nullptr, nullptr, nullptr); + if (_Format == KeyFormat::PKCS1) + newRsaObj = PEM_read_bio_RSAPublicKey(bio_file.GetPointer(), nullptr, nullptr, nullptr); + } + + if (newRsaObj.GetPointer()) { + _RsaObj = static_cast&&>(newRsaObj); + bSuccess = true; + } + + return bSuccess; + } + + template + bool ImportKeyString(const std::string& KeyString) { + static_assert( + _Type == KeyType::PrivateKey || (_Format == KeyFormat::PEM || _Format == KeyFormat::PKCS1), + "Not supported format." + ); + + bool bSuccess = false; + OpenSSLObject bio_mem(BIO_new(BIO_s_mem())); + OpenSSLObject newRsaObj(nullptr); + + if (bio_mem.GetPointer() == nullptr) + return false; + + BIO_puts(bio_mem.GetPointer(), KeyString.c_str()); + + if (_Type == KeyType::PrivateKey) { + newRsaObj = PEM_read_bio_RSAPrivateKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + } else { + if (_Format == KeyFormat::PEM) + newRsaObj = PEM_read_bio_RSA_PUBKEY(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + if (_Format == KeyFormat::PKCS1) + newRsaObj = PEM_read_bio_RSAPublicKey(bio_mem.GetPointer(), nullptr, nullptr, nullptr); + } + + if (newRsaObj.GetPointer()) { + _RsaObj = static_cast&&>(newRsaObj); + bSuccess = true; + } + + return bSuccess; + } + + template + int Encrypt(const void* from, int len, void* to, int padding) { + int write_bytes = 0; + + if (_Type == KeyType::PrivateKey) { + write_bytes = RSA_private_encrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } else { + write_bytes = RSA_public_encrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } + + if (write_bytes == -1) + write_bytes = 0; + return write_bytes; + } + + template + int Decrypt(const void* from, int len, void* to, int padding) { + int write_bytes = 0; + + if (_Type == KeyType::PrivateKey) { + write_bytes = RSA_private_decrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } else { + write_bytes = RSA_public_decrypt(len, + reinterpret_cast(from), + reinterpret_cast(to), + _RsaObj.GetPointer(), + padding); + } + + if (write_bytes == -1) + write_bytes = 0; + return write_bytes; + } + +}; + diff --git a/navicat-patcher/Solution0.cpp b/navicat-patcher/Solution0.cpp new file mode 100644 index 0000000..4b60bb6 --- /dev/null +++ b/navicat-patcher/Solution0.cpp @@ -0,0 +1,84 @@ +#include "Solutions.hpp" +#include "Helper.hpp" + +namespace Patcher { + + const char Solution0::Keyword[452] = + "-----BEGIN PUBLIC KEY-----\x00" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw1dqF3SkCaAAmMzs889I\x00" + "qdW9M2dIdh3jG9yPcmLnmJiGpBF4E9VHSMGe8oPAy2kJDmdNt4BcEygvssEfginv\x00" + "a5t5jm352UAoDosUJkTXGQhpAWMF4fBmBpO3EedG62rOsqMBgmSdAyxCSPBRJIOF\x00" + "R0QgZFbRnU0frj34fiVmgYiLuZSAmIbs8ZxiHPdp1oD4tUpvsFci4QJtYNjNnGU2\x00" + "WPH6rvChGl1IRKrxMtqLielsvajUjyrgOC6NmymYMvZNER3htFEtL1eQbCyTfDmt\x00" + "YyQ1Wt4Ot12lxf0wVIR5mcGN7XCXJRHOFHSf1gzXWabRSvmt1nrl7sW6cjxljuuQ\x00" + "awIDAQAB\x00" + "-----END PUBLIC KEY-----\x00"; + + bool Solution0::FindPatchOffset() noexcept { + bool bFound = false; + + uint8_t* pFileView = pTargetFile->GetView(); + off_t FileSize; + + if (pFileView == nullptr) + return false; + + if (!pTargetFile->GetFileSize(FileSize)) + return false; + + if (FileSize < KeywordLength) + return false; + + FileSize -= KeywordLength; + for (off_t i = 0; i < FileSize; ++i) { + if (pFileView[i] == Keyword[0] && memcmp(pFileView + i, Keyword, KeywordLength) == 0) { + PatchOffset = i; + bFound = true; + break; + } + } + + if (bFound) + printf("MESSAGE: [Solution0] Keyword has been found: offset = +0x%08lx.\n", + static_cast(PatchOffset)); + return bFound; + } + + bool Solution0::MakePatch(RSACipher* cipher) const { + uint8_t* lpTargetFileView = pTargetFile->GetView(); + std::string RSAPublicKeyPEM = + cipher->ExportKeyString(); + + if (RSAPublicKeyPEM.empty()) { + REPORT_ERROR("ERROR: cipher->ExportKeyString failed."); + return false; + } + + for (size_t i = 0; i < RSAPublicKeyPEM.length(); ++i) { + if (RSAPublicKeyPEM[i] == '\n') + RSAPublicKeyPEM[i] = '\x00'; + } + + if (RSAPublicKeyPEM.length() != KeywordLength) { + REPORT_ERROR("ERROR: Public key length does not match."); + return false; + } + + PRINT_MESSAGE("//"); + PRINT_MESSAGE("// Begin Solution0"); + PRINT_MESSAGE("//"); + printf("@+0x%08llX\nPrevious:\n", PatchOffset); + Helper::PrintMemory(lpTargetFileView + PatchOffset, + lpTargetFileView + PatchOffset + KeywordLength, + lpTargetFileView); + + memcpy(lpTargetFileView + PatchOffset, RSAPublicKeyPEM.c_str(), KeywordLength); + + PRINT_MESSAGE("After:"); + Helper::PrintMemory(lpTargetFileView + PatchOffset, + lpTargetFileView + PatchOffset + KeywordLength, + lpTargetFileView); + PRINT_MESSAGE(""); + return true; + } +} \ No newline at end of file diff --git a/navicat-patcher/Solution1.cpp b/navicat-patcher/Solution1.cpp new file mode 100644 index 0000000..9a4a516 --- /dev/null +++ b/navicat-patcher/Solution1.cpp @@ -0,0 +1,116 @@ +#include "Solutions.hpp" +#include "Helper.hpp" + +namespace Patcher { + + const uint8_t Solution1::Keyword[KeywordLength] = { + 0xfe, 0xfd, 0xfc, 0xf4, 0xfe, 0xd2, 0xf8, 0xf4, 0xf1, 0xd3, 0xde, 0xc7, 0xdf, 0xd3, 0xd0, 0xfd, + 0x8a, 0xc3, 0x85, 0xf4, 0xf6, 0xe9, 0xfc, 0xfc, 0xf2, 0xf5, 0xfa, 0xf5, 0xf6, 0xe9, 0x81, 0xfb, + 0xfe, 0xfd, 0xfc, 0xf4, 0xf4, 0xdf, 0xf2, 0xf9, 0xf2, 0xe5, 0xf0, 0xf7, 0xc0, 0x89, 0xdd, 0xcb, + 0xf5, 0x87, 0xe6, 0xdd, 0xf4, 0xd9, 0xf8, 0xfb, 0xde, 0xf9, 0xcf, 0xc5, 0x8f, 0x80, 0x80, 0xf3, + 0xc2, 0xd0, 0xe2, 0x8f, 0xfa, 0x8a, 0xdd, 0xf3, 0xd7, 0xdc, 0x86, 0xdc, 0xf0, 0x81, 0xc0, 0xea, + 0xd0, 0xd9, 0xf9, 0xd8, 0xda, 0xf2, 0xd0, 0xfd, 0xc3, 0xf6, 0xf3, 0x82, 0xf2, 0x81, 0xef, 0xf2, + 0xe0, 0xf9, 0xf2, 0xd3, 0x8f, 0xd7, 0xe9, 0xfb, 0xca, 0x86, 0xde, 0xfc, 0xf3, 0xd5, 0xdd, 0xf4, + 0xc7, 0x80, 0xf7, 0xd5, 0xf2, 0xc1, 0xde, 0xcc, 0xc0, 0xc7, 0xf0, 0xd0, 0xd0, 0xd1, 0xd7, 0xcc, + 0xd2, 0x81, 0xc1, 0x83, 0xdd, 0xd5, 0x8a, 0x8f, 0x81, 0xe1, 0xf4, 0xd9, 0xf3, 0xd7, 0xca, 0xef, + 0xf9, 0xdf, 0xe1, 0xee, 0xf0, 0xe9, 0xd1, 0xca, 0xf2, 0xe3, 0xf8, 0xf0, 0x83, 0xde, 0xfb, 0xd7, + 0xf1, 0xc4, 0xfa, 0x85, 0xf2, 0xdd, 0xdd, 0xfd, 0x85, 0x86, 0xc7, 0xf9, 0xc4, 0xc9, 0xf4, 0xf8, + 0xd4, 0xd9, 0xe6, 0xd2, 0xf6, 0xc1, 0xc1, 0xf9, 0xe0, 0xe4, 0xf7, 0xe4, 0xfd, 0xf1, 0xf6, 0xfc, + 0xe1, 0x84, 0xe4, 0xd1, 0xed, 0xfe, 0xdb, 0xe8, 0xdd, 0xe1, 0x85, 0xd0, 0xc5, 0xd2, 0x8a, 0x8e, + 0xd5, 0xdd, 0xe3, 0xdb, 0xd0, 0xe1, 0xd0, 0xf6, 0xc6, 0xee, 0xe6, 0xf7, 0xda, 0xf1, 0xdb, 0xc9, + 0x8b, 0xee, 0xcd, 0xdf, 0xff, 0xe8, 0xdd, 0xca, 0x82, 0xdb, 0xf1, 0x82, 0xc3, 0xed, 0xc9, 0xcc, + 0xc0, 0xf2, 0xd6, 0xdf, 0x83, 0xe9, 0xf3, 0xce, 0xea, 0xfa, 0xdf, 0xf8, 0xd9, 0xff, 0xec, 0x88, + 0xe4, 0xe4, 0xfd, 0x80, 0xc5, 0xce, 0xfa, 0xd2, 0xf4, 0xd8, 0x84, 0xff, 0xe5, 0xf3, 0xcb, 0xc2, + 0xfe, 0xc0, 0xc4, 0xfa, 0xde, 0xdd, 0xd5, 0xc9, 0xc5, 0xd5, 0xdf, 0xe3, 0xdd, 0xc1, 0xcb, 0xdd, + 0xfc, 0xf7, 0x83, 0xf8, 0xda, 0xc1, 0xd4, 0xe3, 0xfe, 0xc2, 0xef, 0xf8, 0xf2, 0xea, 0x8a, 0xd2, + 0xc7, 0xf2, 0xf0, 0xc2, 0xfb, 0x89, 0xdc, 0xeb, 0xd1, 0xf7, 0xcc, 0xe2, 0xd1, 0xfc, 0xd4, 0xce, + 0xea, 0xcd, 0xe4, 0x87, 0xe0, 0xcc, 0x8d, 0xf5, 0xc7, 0x85, 0x87, 0xda, 0xcf, 0xde, 0x89, 0xcd, + 0xe5, 0xfd, 0xe7, 0x83, 0xda, 0xdb, 0xfe, 0xf4, 0x84, 0xec, 0xf6, 0xee, 0xfd, 0xea, 0xf1, 0xf5, + 0xf5, 0xfc, 0xe6, 0xd0, 0x86, 0xdf, 0xc3, 0xe2, 0xe4, 0xd5, 0xd7, 0xe4, 0xe4, 0xce, 0xd4, 0xce, + 0x82, 0xda, 0xc7, 0xda, 0x80, 0xcb, 0xee, 0x8c, 0xd0, 0xde, 0xcd, 0xda, 0xdd, 0xcd, 0xcc, 0xeb, + 0xd2, 0xc3, 0xfc, 0xf2, 0xf6, 0xe9, 0xf8, 0xf8 + }; + + bool Solution1::FindPatchOffset() noexcept { + bool bFound = false; + + uint8_t* pFileView = pTargetFile->GetView(); + off_t FileSize; + + if (pFileView == nullptr) + return false; + + if (!pTargetFile->GetFileSize(FileSize)) + return false; + + if (FileSize < KeywordLength) + return false; + + FileSize -= KeywordLength; + for (off_t i = 0; i < FileSize; ++i) { + if (pFileView[i] == Keyword[0] && memcmp(pFileView + i, Keyword, KeywordLength) == 0) { + PatchOffset = i; + bFound = true; + break; + } + } + + if (bFound) + printf("MESSAGE: [Solution1] Keyword has been found: offset = +0x%08lx.\n", + static_cast(PatchOffset)); + return bFound; + } + + bool Solution1::MakePatch(RSACipher* cipher) const { + uint8_t* lpTargetFileView = pTargetFile->GetView(); + std::string RSAPublicKeyPEM = + cipher->ExportKeyString(); + + if (RSAPublicKeyPEM.empty()) { + REPORT_ERROR("ERROR: cipher->ExportKeyString failed."); + return false; + } + + RSAPublicKeyPEM.erase(RSAPublicKeyPEM.find("-----BEGIN PUBLIC KEY-----"), 26); + RSAPublicKeyPEM.erase(RSAPublicKeyPEM.find("-----END PUBLIC KEY-----"), 24); + { + std::string::size_type pos = 0; + while ((pos = RSAPublicKeyPEM.find("\n", pos)) != std::string::npos) { + RSAPublicKeyPEM.erase(pos, 1); + } + } + + if (RSAPublicKeyPEM.length() != KeywordLength) { + REPORT_ERROR("ERROR: Public key length does not match."); + return false; + } + + { + uint8_t key = 8; + for (size_t i = 0; i < RSAPublicKeyPEM.length(); ++i) { + if (key == 0) + key = 8; + RSAPublicKeyPEM[i] ^= 0xbb - key; + --key; + } + } + + PRINT_MESSAGE("//"); + PRINT_MESSAGE("// Begin Solution1"); + PRINT_MESSAGE("//"); + printf("@+0x%08llX\nPrevious:\n", PatchOffset); + Helper::PrintMemory(lpTargetFileView + PatchOffset, + lpTargetFileView + PatchOffset + KeywordLength, + lpTargetFileView); + + memcpy(lpTargetFileView + PatchOffset, RSAPublicKeyPEM.c_str(), KeywordLength); + + PRINT_MESSAGE("After:"); + Helper::PrintMemory(lpTargetFileView + PatchOffset, + lpTargetFileView + PatchOffset + KeywordLength, + lpTargetFileView); + PRINT_MESSAGE(""); + return true; + } +} + diff --git a/navicat-patcher/Solutions.hpp b/navicat-patcher/Solutions.hpp new file mode 100644 index 0000000..ab0adb9 --- /dev/null +++ b/navicat-patcher/Solutions.hpp @@ -0,0 +1,78 @@ +#pragma once +#include "FileMapper.hpp" +#include "RSACipher.hpp" + +namespace Patcher { + + class Solution { + public: + virtual void SetFile(FileMapper* pFile) = 0; + virtual bool CheckKey(RSACipher* cipher) const = 0; + virtual bool FindPatchOffset() = 0; + virtual bool MakePatch(RSACipher* cipher) const = 0; + virtual ~Solution() = default; + }; + + // Solution0 will replace the RSA public key stored in main application. + class Solution0 : public Solution { + private: + static constexpr size_t KeywordLength = 451; + static const char Keyword[KeywordLength + 1]; + + FileMapper* pTargetFile; + off_t PatchOffset; + public: + + Solution0() noexcept : + pTargetFile(nullptr), + PatchOffset(-1) {} + + virtual void SetFile(FileMapper* pMainApp) noexcept override { + pTargetFile = pMainApp; + } + + // Solution0 does not have any requirements for an RSA-2048 key + virtual bool CheckKey(RSACipher* cipher) const noexcept override { + return true; + } + + // Return true if found, other return false + virtual bool FindPatchOffset() noexcept override; + + // Make a patch based on an RSA private key given + // Return true if success, otherwise return false + virtual bool MakePatch(RSACipher* cipher) const override; + }; + + // Solution1 will replace the RSA public key stored in main application. + class Solution1 : public Solution { + private: + static constexpr size_t KeywordLength = 0x188; + static const uint8_t Keyword[KeywordLength]; + + FileMapper* pTargetFile; + off_t PatchOffset; + public: + Solution1() : + pTargetFile(nullptr), + PatchOffset(-1) {} + + virtual void SetFile(FileMapper* pLibccFile) noexcept override { + pTargetFile = pLibccFile; + } + + // Solution1 has no requirements for an RSA-2048 key + virtual bool CheckKey(RSACipher* cipher) const noexcept override { + return true; + } + + // Return true if found, otherwise return false + virtual bool FindPatchOffset() noexcept override; + + // Make a patch based on an RSA private key given + // Return true if success, otherwise return false + virtual bool MakePatch(RSACipher* cipher) const override; + }; + +} + diff --git a/navicat-patcher/main.c b/navicat-patcher/main.c deleted file mode 100644 index 2511ac6..0000000 --- a/navicat-patcher/main.c +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -const char pubkey[9][72] = { - "-----BEGIN PUBLIC KEY-----", - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB", - "oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S", - "oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe", - "b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2", - "pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4", - "naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO", - "1QIDAQAB", - "-----END PUBLIC KEY-----" -}; - -void help() { - printf("Usage:\n"); - printf(" ./navicat-patcher \n"); - printf("\n"); -} - -size_t search_pubkey_location(uint8_t* pFileContent, size_t FileSize) { - static const char search_str[] = "-----BEGIN PUBLIC KEY-----"; - - size_t i = 0; - if (FileSize < sizeof(search_str) - 1) return (size_t)-1; - FileSize -= sizeof(search_str); - for (; i < FileSize; ++i) { - if (pFileContent[i] == '-' && memcmp(pFileContent + i, search_str, sizeof(search_str) - 1) == 0) - return i; - } - return (size_t)-1; -} - -void do_patch(uint8_t* pFileContent, size_t offset) { - strcpy(pFileContent + offset, pubkey[0]); - offset += strlen(pubkey[0]) + 1; - - strcpy(pFileContent + offset, pubkey[1]); - offset += strlen(pubkey[1]) + 1; - - strcpy(pFileContent + offset, pubkey[2]); - offset += strlen(pubkey[2]) + 1; - - strcpy(pFileContent + offset, pubkey[3]); - offset += strlen(pubkey[3]) + 1; - - strcpy(pFileContent + offset, pubkey[4]); - offset += strlen(pubkey[4]) + 1; - - strcpy(pFileContent + offset, pubkey[5]); - offset += strlen(pubkey[5]) + 1; - - strcpy(pFileContent + offset, pubkey[6]); - offset += strlen(pubkey[6]) + 1; - - strcpy(pFileContent + offset, pubkey[7]); - offset += strlen(pubkey[7]) + 1; - - strcpy(pFileContent + offset, pubkey[8]); - offset += strlen(pubkey[8]) + 1; -} - -int main(int argc, char* argv[], char* envp[]) { - int status = 0; - int fd = -1; - struct stat fd_stat = {}; - uint8_t* file_content = 0; - - if (argc != 2) { - help(); - return 0; - } - - fd = open(argv[1], O_RDWR, S_IRUSR | S_IWUSR); - if (fd == -1) { - printf("Failed to open file. CODE: 0x%08x\n", errno); - status = errno; - goto main_fin; - } else { - printf("Open file successfully.\n"); - } - - if (fstat(fd, &fd_stat) != 0) { - printf("Failed to get file size. CODE: 0x%08x\n", errno); - status = errno; - goto main_fin; - } else { - printf("Get file size successfully: %zu\n", fd_stat.st_size); - } - - file_content = mmap(NULL, fd_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - if (file_content == (void*)-1) { - printf("Failed to map file. CODE: 0x%08x\n", errno); - status = errno; - goto main_fin; - } else { - printf("Map file successfully.\n"); - } - - size_t offset = search_pubkey_location(file_content, fd_stat.st_size); - if (offset == (size_t)-1) { - printf("Failed to find pubkey location.\n"); - goto main_fin; - } - - printf("offset = 0x%016llx\n", offset); - - do_patch(file_content, offset); - printf("Success!\n"); - -main_fin: - if (file_content != NULL) munmap(file_content, fd_stat.st_size); - if (fd != -1) close(fd); - return status; -} - diff --git a/navicat-patcher/main.cpp b/navicat-patcher/main.cpp new file mode 100644 index 0000000..68d61cc --- /dev/null +++ b/navicat-patcher/main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include + +#include "Helper.hpp" +#include "FileMapper.hpp" +#include "RSACipher.hpp" +#include "Solutions.hpp" + +#define SAFE_DELETE(x) { delete x; x = nullptr; } + +void help() { + puts("Usage:"); + puts(" ./navicat-patcher [RSA-2048 PrivateKey(PEM file)]"); + puts(""); +} + +bool LoadKey(RSACipher* cipher, const char* filename, + Patcher::Solution* pSolution0, + Patcher::Solution* pSolution1) { + if (filename) { + if (!cipher->ImportKeyFromFile(filename)) { + REPORT_ERROR("ERROR: cipher->ImportKeyFromFile failed."); + return false; + } + + if ((pSolution0 && !pSolution0->CheckKey(cipher)) || + (pSolution1 && !pSolution1->CheckKey(cipher))) { + REPORT_ERROR("ERROR: The RSA private key you provided cannot be used."); + return false; + } + } else { + PRINT_MESSAGE(""); + PRINT_MESSAGE("MESSAGE: Generating new RSA private key, it may take a long time."); + + do { + cipher->GenerateKey(2048); + } while ((pSolution0 && !pSolution0->CheckKey(cipher)) || + (pSolution1 && !pSolution1->CheckKey(cipher))); // re-generate RSA key if one of CheckKey return false + + if (!cipher->ExportKeyToFile("RegPrivateKey.pem")) { + REPORT_ERROR("ERROR: Failed to save RSA private key."); + return false; + } + + PRINT_MESSAGE("MESSAGE: New RSA private key has been saved to RegPrivateKey.pem."); + } + + std::string PublicKeyString = cipher->ExportKeyString(); + if (PublicKeyString.empty()) { + REPORT_ERROR("ERROR: cipher->ExportKeyString failed."); + return false; + } + + PRINT_MESSAGE(""); + PRINT_MESSAGE("Your RSA public key:"); + PRINT_MESSAGE(PublicKeyString.c_str()); + return true; +} + +int main(int argc, char* argv[], char* envp[]) { + int status = 0; + FileMapper MainApp; + off_t MainAppSize; + Helper::ResourceGuard cipher(nullptr); + Helper::ResourceGuard pSolution0(nullptr); + Helper::ResourceGuard pSolution1(nullptr); + + if (argc != 2 && argc != 3) { + help(); + return status; + } + + PRINT_MESSAGE("NOTICE:"); + printf("This patcher will modify the file: %s\n", argv[1]); + PRINT_MESSAGE("Please make a backup by your own if you care. Otherwise just ignore this notice."); + PRINT_MESSAGE("Press Enter to continue OR Ctrl+C to abort..."); + getchar(); + + cipher.ptr = RSACipher::Create(); + if (cipher.ptr == nullptr) { + REPORT_ERROR("ERROR: RSACipher::Create failed."); + return status; + } + pSolution0.ptr = new Patcher::Solution0(); + pSolution1.ptr = new Patcher::Solution1(); + + // + // Map file + // + if (!MainApp.OpenFile(argv[1])) { + status = errno; + REPORT_ERROR_WITH_CODE("Failed to open file."); + return status; + } else { + PRINT_MESSAGE("MESSAGE: Open file successfully."); + } + + if (!MainApp.GetFileSize(MainAppSize)) { + status = errno; + REPORT_ERROR_WITH_CODE("Failed to get file size."); + return status; + } else { + printf("MESSAGE: Get file size successfully: %lld\n", MainAppSize); + } + + if (!MainApp.Map(static_cast(MainAppSize))) { + status = errno; + REPORT_ERROR_WITH_CODE("Failed to map file."); + return status; + } else { + PRINT_MESSAGE("MESSAGE: Map file successfully."); + } + + pSolution0.ptr->SetFile(&MainApp); + pSolution1.ptr->SetFile(&MainApp); + + // + // Find patch offsets + // + if (!pSolution0.ptr->FindPatchOffset()) { + PRINT_MESSAGE("MESSAGE: Solution0: Cannot find public key. Solution0 will be omitted."); + pSolution0.ptr->SetFile(nullptr); + SAFE_DELETE(pSolution0.ptr); + } + + if (!pSolution1.ptr->FindPatchOffset()) { + PRINT_MESSAGE("MESSAGE: Solution1: Cannot find public key. Solution1 will be omitted."); + pSolution1.ptr->SetFile(nullptr); + SAFE_DELETE(pSolution1.ptr); + } + + // + // Load or generate RSA-2048 key + // + if (!LoadKey(cipher.ptr, argc == 3 ? argv[2] : nullptr, pSolution0.ptr, pSolution1.ptr)) + return status; + + // + // Making patch + // + if (pSolution0.ptr && !pSolution0.ptr->MakePatch(cipher.ptr)) + return status; + if (pSolution1.ptr && !pSolution1.ptr->MakePatch(cipher.ptr)) + return status; + + // + // Report result + // + if (pSolution0.ptr) + PRINT_MESSAGE("Solution0 has been done successfully."); + if (pSolution1.ptr) + PRINT_MESSAGE("Solution1 has been done successfully."); + + return status; +} diff --git a/navicat-patcher/rpk b/navicat-patcher/rpk deleted file mode 100644 index b213dd1..0000000 --- a/navicat-patcher/rpk +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxqkTcfbKw8ysVygePlcB -oUAhCF6oniyP13iDtu85ZsHwqw8PnMyTp6n6FnMN9YinleIAy6NFveBu/vshTN8S -oXbYyy5AqdZ8CQpfvuriO9UNfgV1l7SFdPPpruFAmOw+uzA3GawMsg3QNK/htqJe -b4xKHFS04xC2AueE2RTmk6tJcL8TEBfRG7DEYOHPjebKl1NQ3ZIu15U97cCPYKO2 -pWHzsb+Fr4Wj0DChLoxlXxaBcJ2ozogaq0tW2t4Aopvt9kRSuSK9HcgxICJM5ct4 -naU91WFGWlw0+0JpiMIl5OnMbpak/5xQre9DL8zM8LjRy14I88txvXvhPEsWaYCO -1QIDAQAB ------END PUBLIC KEY-----