├── README.md ├── LICENSE ├── private.key └── melondecrypt.py /README.md: -------------------------------------------------------------------------------- 1 | # Melon Books Decrypt 2 | 3 | ## Introduction 4 | This is a script to convert DRM-protected files (*.melon) downloaded from Melon Books to normal files. 5 | 6 | ## Dependencies 7 | * [pycryptodome](https://pycryptodome.readthedocs.io/en/latest/src/installation.html) 8 | 9 | ## Usage 10 | ``` 11 | python melondecrypt.py -e email_address -p login_password [-o output_directory] melon_file_path 12 | ``` 13 | 14 | ## Notice 15 | * You must have purchased the book before you use the script. 16 | * This script is intended for personal use only. Please respect the copyright of the books you purchase. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 HNIdesu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEA0kPvGMann/WD+FTklhQJoCaYx+HivLye0UmtiARXXxR9aEsK 3 | 2TniZpBQQPqu5Zy5bECrdNZszf+Nd5/v9+dNL5MMwcpstkUnC+3KztfTvLv4z7ml 4 | 8W/BsEZIWF4NIHVKc8WwU/CfLUlrpOtR9UVEEUJxQNG8bSabHsDtnd++Y2iHtk9C 5 | cQ8Wyx63bcHtFH3j2Gz196AaPgX06ZSBLg5jzTxAL0oFo7lS1kubUbEsGGYfi5sT 6 | rNQFcqtFnleYFS0EPQzJHKcHE8RV+MEHzCQAQut9r1Pvgoq7I61voFZav5tcbTn8 7 | qkIFxh1gH+rdw9KNstSLpESwGBwX3tXDT40jHwIDAQABAoIBAQCPeIc20qYiAXaR 8 | BewLc5S10GBDUyBRRCriB3H+82dimyqO9VVrr5GKBkKcc/DO/8eN0/qp+H25efxf 9 | eaRV9V+3bJEx0hunqzvfadwOkzoI+AHoS3atbWIuE2bd1R8D3MR2hpewTuluD0EC 10 | AQelYiP12u5IGlSF7ee+bko0hSz1ymrOaVw7DCqLIkZ51TLRLpuZFwQ1eOQkwj8x 11 | OoT5Lb4UV1Af4ym6FK/bcDmDsdeM+c4WmorEpy9MIMjDOwzY5chnbWhZ4rtCfGEx 12 | owgJ1u91aEq1Y3/26tKkAz9HmsovoZVzwYK6KI2f6o6f/ShsMGzVoqC+gfzi2558 13 | IYSMwkWhAoGBAO0V1LvsK/pLQO0vHjq15DTBspKlnEj3AZ0p0GUi616PUB0bmotp 14 | 0392VchySKXFmUqRMiOletPk2+48Qb66zHvXLiBBnvXLjKyMV2uQPeo9AEVbWe1O 15 | d9Zhp8lg69/2zyjgc+wbcCNlOjdNvPekmqed9PTd7orAKGpCFbNEi4CnAoGBAOMK 16 | Vwop1pZ/ioUT1N5m+SVIs6XxP6epDpoRWHisApC4NPQO6kLzwW2QnspX31orXDVS 17 | 16tqkf615O4X2+/XF9ruPwUkkM17g2zYDFsR2bxqymTBCDuNPPwNizZS3kgGtOJI 18 | 8PC91fM6v3fanmwqUc2dEZBhIUOTsU8b/SRaVeDJAoGBAIKLQMXw9w4snaV8ClMd 19 | kHiUJzWkRvfOOm9FdOAbaCp7EwvUBTa6oKBQkk96zhGpSgzLAiaqmYVPQOJe/3x7 20 | 8thF1bohJ9wDpoPkCsbJd64gxKNr94o5aLb8spyp53c8uTiyzmG1gfubY4DMJz7c 21 | veOJkDW8dfqkcByCItzA42eFAoGAYLk2+413bgZH7QnV1inoWonufvOYOsU7A0gA 22 | eOhO73iKlq8D/iH6dcL24x0sei2eytjQKKbuK6UyMnzXpgXsk8iL8JTzW5cTFnu0 23 | R9gC+tJEB0h1SWmSYY8jj7EeIXE5/m00uFsnmsecamMouswMrZwMr4WxtihlkV5L 24 | KgGSNIECgYEAwhyFFXsy79ToA7B4qT/GU3A96I1Yrv0tqSZFYQg6jUfRimAIX2dA 25 | 4Ls0tEk/7sUYlUNldoEN4f/75LL9/s8iHpOlytrgsma1O+Pi3eggYad7KwmoT0+z 26 | VOCw3Cj/fx6RPKL3dDifBX+OHDZ299W5l5cmC+shAoPjDp1LlyNE8GY= 27 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /melondecrypt.py: -------------------------------------------------------------------------------- 1 | import json 2 | import xml.etree.ElementTree as ET 3 | import sys 4 | import ssl 5 | import os 6 | import pathlib 7 | import os.path as Path 8 | from io import BytesIO 9 | from urllib.request import urlopen,Request 10 | from urllib.parse import urlencode 11 | from argparse import ArgumentParser 12 | 13 | from Crypto.Cipher import AES 14 | from Crypto.Cipher.AES import MODE_CBC 15 | from Crypto.Util.Padding import unpad 16 | from Crypto.PublicKey import RSA 17 | from Crypto.Cipher import PKCS1_v1_5 18 | 19 | parser=ArgumentParser() 20 | parser.add_argument("filepath",help="the path of the melon file") 21 | parser.add_argument("-e","--email",required=True,help="login email adress") 22 | parser.add_argument("-p","--password",required=True,help="login password") 23 | parser.add_argument("-o","--output",required=False,help="output directroy") 24 | args=parser.parse_args(sys.argv[1:]) 25 | 26 | filepath=args.filepath 27 | email=args.email 28 | password=args.password 29 | 30 | def resource_path(resource_name): 31 | if hasattr(sys, '_MEIPASS'): 32 | return os.path.join(sys._MEIPASS, resource_name) 33 | return os.path.join(Path.dirname(__file__), resource_name) 34 | 35 | if args.output: 36 | output_directory=args.output 37 | else: 38 | output_directory="output" 39 | if not Path.exists(output_directory): 40 | os.makedirs(output_directory,exist_ok=True) 41 | content_id=None 42 | data_chunk=None 43 | encrypt_key=None 44 | filetype=None 45 | with open(filepath,"rb") as br: 46 | filesize=br.seek(0,2) 47 | br.seek(0,0) 48 | if not br.read(4)==b'RIFF': 49 | quit() 50 | br.seek(8,0) 51 | if not br.read(4)==b'BeBG': 52 | quit() 53 | br.seek(12,0) 54 | while br.tell()!=filesize: 55 | chunk_header=br.read(4).decode("utf-8") 56 | chunk_size=int.from_bytes(br.read(4),byteorder="little") 57 | chunk_data=br.read(chunk_size) 58 | if(chunk_size%2!=0): 59 | br.seek(1,1) 60 | if chunk_header=="META": 61 | xml=ET.fromstring(chunk_data.decode("utf-8")) 62 | content_id=xml.find("content_id").text 63 | filetype=xml.find("file_type").text 64 | elif chunk_header=="KEY ": 65 | xml=ET.fromstring(chunk_data.decode("utf-8")) 66 | key_text=xml.find("key").text 67 | iv=bytes.fromhex(key_text[0:32]) 68 | encrypt_key=bytes.fromhex(key_text[32:]) 69 | elif chunk_header=="data": 70 | data_chunk=chunk_data 71 | if encrypt_key==None: 72 | print("key not found") 73 | quit() 74 | 75 | with open(resource_path('private.key'), 'rb') as key_file: 76 | private_key = RSA.importKey(key_file.read()) 77 | context = ssl.create_default_context() 78 | context.set_ciphers("DEFAULT:@SECLEVEL=1") 79 | response=json.loads(urlopen(Request("https://api.melonbooks.co.jp/app/auth.php",data=urlencode({ 80 | "mailaddress":email, 81 | "password":password, 82 | "device":"", 83 | "content_id":content_id, 84 | "access_key":"S4vQVkERvGkxpKZA", 85 | "platform":"windows" 86 | }).encode("utf-8"),headers={ 87 | "Content-Type":"application/x-www-form-urlencoded", 88 | "User-Agent":"Mozilla/5.0", 89 | "Accept-Language":"en,*" 90 | }),context=context).read().decode("utf-8")) 91 | if not response["melonbooks"]["status"]["code"]==200: 92 | print("request drm_key fail") 93 | print(response) 94 | quit() 95 | encrypted_drm_key=bytes.fromhex(json.loads(response["melonbooks"]["result"]["orders"][0]["drm_key"])["data"]["key"]) 96 | drm_key=PKCS1_v1_5.new(private_key).decrypt(encrypted_drm_key,sentinel=None) 97 | aes=AES.new(drm_key,MODE_CBC,iv=iv) 98 | key=unpad(aes.decrypt(encrypt_key),AES.block_size) 99 | 100 | dest_filepath=Path.join(output_directory,pathlib.Path(filepath).with_suffix(f".{filetype}").name) 101 | with BytesIO(data_chunk) as br: 102 | chunk_length=len(data_chunk) 103 | with open(dest_filepath,"wb") as bw: 104 | iv=br.read(16) 105 | encrypted_data=br.read() 106 | data=AES.new(key,MODE_CBC,iv).decrypt(encrypted_data) 107 | bw.write(data) 108 | print(f"the decrypted file has been saved to {dest_filepath}") --------------------------------------------------------------------------------