generate_passwords.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. #!/usr/bin/env python3
  2. """
  3. Password Generator for Emergency Access Server
  4. Generates secure passwords and their hashes for HTTP Basic Authentication
  5. """
  6. import secrets
  7. import string
  8. import hashlib
  9. import argparse
  10. import json
  11. import getpass
  12. from typing import Dict, Any
  13. def generate_secure_password(length: int = 64) -> str:
  14. """
  15. Generate a cryptographically secure password.
  16. Args:
  17. length: Password length (default: 16)
  18. Returns:
  19. Secure password string
  20. """
  21. # Use a mix of letters, numbers, and safe symbols
  22. alphabet = string.ascii_letters + string.digits + "!@#$%^&*"
  23. return ''.join(secrets.choice(alphabet) for _ in range(length))
  24. def generate_password_hash(password: str) -> str:
  25. """
  26. Generate a password hash using PBKDF2 with SHA-256.
  27. Args:
  28. password: Plain text password
  29. Returns:
  30. Password hash in format "salt:hash"
  31. """
  32. salt = secrets.token_hex(16)
  33. password_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
  34. return f"{salt}:{password_hash.hex()}"
  35. def verify_password(password: str, password_hash: str) -> bool:
  36. """
  37. Verify a password against its hash.
  38. Args:
  39. password: Plain text password
  40. password_hash: Hash in format "salt:hash"
  41. Returns:
  42. True if password matches, False otherwise
  43. """
  44. try:
  45. salt, stored_hash = password_hash.split(':')
  46. computed_hash = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
  47. return computed_hash.hex() == stored_hash
  48. except Exception:
  49. return False
  50. def generate_key_credentials(key_id: str) -> Dict[str, str]:
  51. """
  52. Generate username and password for a key.
  53. Args:
  54. key_id: Key identifier
  55. Returns:
  56. Dictionary with username, password, and password_hash
  57. """
  58. username = f"emergency_{key_id}"
  59. password = generate_secure_password(64) # Longer for emergency keys
  60. password_hash = generate_password_hash(password)
  61. return {
  62. "username": username,
  63. "password": password,
  64. "password_hash": password_hash
  65. }
  66. def generate_health_credentials() -> Dict[str, str]:
  67. """
  68. Generate username and password for health endpoint.
  69. Returns:
  70. Dictionary with username, password, and password_hash
  71. """
  72. username = "health_monitor"
  73. password = generate_secure_password(16)
  74. password_hash = generate_password_hash(password)
  75. return {
  76. "username": username,
  77. "password": password,
  78. "password_hash": password_hash
  79. }
  80. def generate_full_credentials(keys: list) -> Dict[str, Any]:
  81. """
  82. Generate all credentials for a configuration.
  83. Args:
  84. keys: List of key identifiers
  85. Returns:
  86. Dictionary with all credentials
  87. """
  88. credentials = {
  89. "health": generate_health_credentials(),
  90. "keys": {}
  91. }
  92. for key_id in keys:
  93. credentials["keys"][key_id] = generate_key_credentials(key_id)
  94. return credentials
  95. def update_config_with_credentials(config_path: str, credentials: Dict[str, Any]) -> None:
  96. """
  97. Update a configuration file with generated credentials.
  98. Args:
  99. config_path: Path to configuration file
  100. credentials: Generated credentials
  101. """
  102. try:
  103. with open(config_path, 'r') as f:
  104. config = json.load(f)
  105. except FileNotFoundError:
  106. print(f"Configuration file {config_path} not found")
  107. return
  108. except json.JSONDecodeError:
  109. print(f"Invalid JSON in {config_path}")
  110. return
  111. # Update health credentials
  112. if "routes" not in config:
  113. config["routes"] = {}
  114. config["routes"]["health_username"] = credentials["health"]["username"]
  115. config["routes"]["health_password_hash"] = credentials["health"]["password_hash"]
  116. # Update key credentials
  117. if "keys" in config:
  118. for key_id, key_creds in credentials["keys"].items():
  119. if key_id in config["keys"]:
  120. config["keys"][key_id]["username"] = key_creds["username"]
  121. config["keys"][key_id]["password_hash"] = key_creds["password_hash"]
  122. # Write updated config
  123. with open(config_path, 'w') as f:
  124. json.dump(config, f, indent=2)
  125. print(f"Updated configuration file: {config_path}")
  126. def print_credentials(credentials: Dict[str, Any], show_passwords: bool = True) -> None:
  127. """
  128. Print generated credentials in a readable format.
  129. Args:
  130. credentials: Generated credentials
  131. show_passwords: Whether to show plain text passwords
  132. """
  133. print("\n" + "=" * 60)
  134. print("GENERATED CREDENTIALS")
  135. print("=" * 60)
  136. print("\nHealth Endpoint:")
  137. print(f" Username: {credentials['health']['username']}")
  138. if show_passwords:
  139. print(f" Password: {credentials['health']['password']}")
  140. print(f" Hash: {credentials['health']['password_hash']}")
  141. print("\nKey Endpoints:")
  142. for key_id, key_creds in credentials["keys"].items():
  143. print(f"\n {key_id}:")
  144. print(f" Username: {key_creds['username']}")
  145. if show_passwords:
  146. print(f" Password: {key_creds['password']}")
  147. print(f" Hash: {key_creds['password_hash']}")
  148. print("\n" + "=" * 60)
  149. if show_passwords:
  150. print("⚠️ SECURITY WARNING:")
  151. print(" Store these passwords securely!")
  152. print(" The plain text passwords are shown only once.")
  153. print(" Consider using a password manager.")
  154. print("\n💡 Usage Examples:")
  155. print(f" curl -u {credentials['health']['username']}:PASSWORD http://localhost:1127/health-check")
  156. for key_id, key_creds in credentials["keys"].items():
  157. print(f" curl -u {key_creds['username']}:PASSWORD http://localhost:1127/emergency-key-{key_id}")
  158. def interactive_password_setup() -> Dict[str, Any]:
  159. """
  160. Interactive setup for custom passwords.
  161. Returns:
  162. Dictionary with credentials using user-provided passwords
  163. """
  164. print("\n" + "=" * 60)
  165. print("INTERACTIVE PASSWORD SETUP")
  166. print("=" * 60)
  167. credentials = {"keys": {}}
  168. # Health endpoint password
  169. print("\nHealth Endpoint Setup:")
  170. while True:
  171. password = getpass.getpass("Enter password for health endpoint: ")
  172. confirm = getpass.getpass("Confirm password: ")
  173. if password == confirm and len(password) >= 8:
  174. credentials["health"] = {
  175. "username": "health_monitor",
  176. "password": password,
  177. "password_hash": generate_password_hash(password)
  178. }
  179. break
  180. elif password != confirm:
  181. print("❌ Passwords don't match. Try again.")
  182. else:
  183. print("❌ Password must be at least 8 characters. Try again.")
  184. # Key passwords
  185. print("\nKey Endpoint Setup:")
  186. key_count = int(input("How many keys do you want to configure? "))
  187. for i in range(key_count):
  188. key_id = input(f"Enter key ID for key {i+1} (e.g., 'backup', 'master'): ").strip()
  189. while True:
  190. password = getpass.getpass(f"Enter password for {key_id}: ")
  191. confirm = getpass.getpass("Confirm password: ")
  192. if password == confirm and len(password) >= 12:
  193. credentials["keys"][key_id] = {
  194. "username": f"emergency_{key_id}",
  195. "password": password,
  196. "password_hash": generate_password_hash(password)
  197. }
  198. break
  199. elif password != confirm:
  200. print("❌ Passwords don't match. Try again.")
  201. else:
  202. print("❌ Key passwords must be at least 12 characters. Try again.")
  203. return credentials
  204. def main():
  205. parser = argparse.ArgumentParser(
  206. description="Generate secure passwords and hashes for Emergency Access Server",
  207. formatter_class=argparse.RawDescriptionHelpFormatter,
  208. epilog="""
  209. Examples:
  210. # Generate credentials for specific keys
  211. python generate_passwords.py --keys backup master recovery
  212. # Generate and update config file
  213. python generate_passwords.py --keys backup master --update-config config.json
  214. # Interactive setup
  215. python generate_passwords.py --interactive
  216. # Hash a specific password
  217. python generate_passwords.py --hash-password "my_secure_password"
  218. # Verify a password against a hash
  219. python generate_passwords.py --verify-password "password" --hash "salt:hash"
  220. """
  221. )
  222. parser.add_argument(
  223. '--keys', '-k',
  224. nargs='+',
  225. help='Key identifiers to generate credentials for'
  226. )
  227. parser.add_argument(
  228. '--update-config', '-u',
  229. metavar='CONFIG_FILE',
  230. help='Update configuration file with generated credentials'
  231. )
  232. parser.add_argument(
  233. '--interactive', '-i',
  234. action='store_true',
  235. help='Interactive password setup'
  236. )
  237. parser.add_argument(
  238. '--hash-password',
  239. metavar='PASSWORD',
  240. help='Generate hash for a specific password'
  241. )
  242. parser.add_argument(
  243. '--verify-password',
  244. metavar='PASSWORD',
  245. help='Verify a password against a hash (use with --hash)'
  246. )
  247. parser.add_argument(
  248. '--hash',
  249. metavar='HASH',
  250. help='Hash to verify password against'
  251. )
  252. parser.add_argument(
  253. '--hide-passwords',
  254. action='store_true',
  255. help='Don\'t show plain text passwords in output'
  256. )
  257. parser.add_argument(
  258. '--length',
  259. type=int,
  260. default=16,
  261. help='Password length for generated passwords (default: 16)'
  262. )
  263. args = parser.parse_args()
  264. # Handle specific operations
  265. if args.hash_password:
  266. password_hash = generate_password_hash(args.hash_password)
  267. print(f"Password hash: {password_hash}")
  268. return
  269. if args.verify_password and args.hash:
  270. is_valid = verify_password(args.verify_password, args.hash)
  271. print(f"Password verification: {'✅ VALID' if is_valid else '❌ INVALID'}")
  272. return
  273. # Generate credentials
  274. if args.interactive:
  275. credentials = interactive_password_setup()
  276. elif args.keys:
  277. credentials = generate_full_credentials(args.keys)
  278. else:
  279. # Default: generate for common keys
  280. credentials = generate_full_credentials(['backup', 'master'])
  281. # Print results
  282. print_credentials(credentials, show_passwords=not args.hide_passwords)
  283. # Update config if requested
  284. if args.update_config:
  285. update_config_with_credentials(args.update_config, credentials)
  286. # Save credentials to file
  287. credentials_file = "emergency_credentials.json"
  288. with open(credentials_file, 'w') as f:
  289. json.dump(credentials, f, indent=2)
  290. print(f"\n💾 Credentials saved to: {credentials_file}")
  291. print("⚠️ Remember to store this file securely and delete it after copying passwords!")
  292. if __name__ == '__main__':
  293. main()