ctf-writeups

Peppernuts

Difficulty: Easy-Medium
Author: Bond

New to baking? πŸ₯£πŸ€· Then start small!πŸ’‘And it doesn't get much smaller than the traditional Danish Christmas cookie: The peppernut 🌰 πŸŽ…

I know what you're thinking: Pepper? In a cookie?! Do they actually do that?!? Yes, yes we do - but just a small bit πŸ˜‰ And that little kick is what makes them so good πŸ˜‹

So give them a try, you might just find your new favorite recipe! πŸ₯‡

(PS: Your favourite recipe is of course Brunner's recipe πŸ˜‰)

Attachment:

# peppernut_recipes.csv
username,nonce,hash_salt,key_salt,encrypted_recipe
Alice,394acaf0e2550e2dc5bfacac,a85fa1f0edbb9afd0ac90f6439f52d88,2c8746070bd2ae310727efc0798d92ca,c9bcc3e039708bbc7edfff47782f4f3737addf9896bfb8b63d1bd03434060efc519ad260bfea942dacf90468654548bb7fd1a3b0095f6c11ba15186beafe4a1872eb85a6340438e987bf0e8cb9713486ec9478d8c51e66e185f0ca757754505d902129e5a2067870fb8120e71683cbb51929ae823d98dc24a4e0f752bfb6351390b90d4b6e79b2f135976d4a1035607ed2d8ecf5e7983eb89b0de18b7e19e4cfc0ea0f2291296a5b947ece929c942db684426559c0c1b7f570704a1822c0b0c0eddb93ddf459d3c401d61e3947837a954017c097897a13f9b106874e01e0dff580e0902b644b6e8639ba4062158e9c59ea1860f9a7f5bfe3e55225747edf944bb772b39eefae3320ce040987e63738e1db55610274c020a33af00a4e05372cec7b8b011114c77ee80f80279ffeb026cd53add81bd8d89a4657bad49b2fa5bf31aea991729b880c457da0e2ec359550a1e57a9079e390b406c79abcf3db7e6bd14f94341e53628476e29bc791c1e94137f702f8e6f1c547838fc3f9b6e055544f687038318054891d3f0c05b56e885b29557006019433506d2244f0a2754fc30baff0f2a9f695e67fae614edc6e752c097617180f450604b8210bfbd2dc10ba0ea61853e0dbc5fc4c73d0fb5fc1a548ec37810d0780c70537dfafd0f46dda870c7c2bd26c639b516083fc8a0602afc2ea013884bc7073c12ccf8ec51d435aee9aed0669565d97ada1d6797e90a41106824f85d1ab76cd1312ea157511f76d69e898dc3133df3380d0978e5fbbfe669951db1c100c84c34b7df409708b2d379f312f5f8c45c090b37ff6d540a49c67d0871b1fff17e31c754529c85982fc7d153620365c5bb6526b868b3215d8d15b0678fff305a24b209f0e4fba6e671f81485c00d41df0c33f8182189f6e9b7387dc522b5a5c045f5caf51c8105c46e699cc597d4583e9d1d1a187ac890c03b95c96d950823614fd0a941737a8d5c8757fc8b9ffe83284de736cc1098b6e6ed958c7d13ceba9c1f767a09acbf48f0fda89f88ae9a9356a08764f852ceec65325b44df4925e2fb0ca8a1afa69e84d6ce1c072eefdd1c30dc6c2428acac0ee7f846523ecafdc55a2cd31fac5952c383a5bbb7a86c75f4d5dcf39513507106c5052688b7b408019fe9f5021058fdd41f35e051e13dcbc050676fa538503b61082c74df815111e913ea9bbbea1625b2a5a6e2a240973dbe301da8380f81b1031dc588fc81a5f8a5d79a60afdf1b7976769fdfbfd801078f4558627433d48d67237cc0ca4ddbb0009b35e2e970383aaeeaaf7d4f14f58e971e00e968b5e50df36deb9fc4ca8401bca1451646a5a21128d1c0b330569b4edf1fe1cd25553b5e68b51f8c9897e1ab6c36517b3467364a03d8cbe1eeb5fb4d8f01c4cd20ed3a754953bebd9fe8a937b0260fd35198fb1980a61dba7d883877b297a7879bde77ee7e66201d262b03b6f7477113ab2ab1a88f8d8a0266dc01f29c5cecd7d08e5c32d6a8f0720e62ff8aa201b2cdde949abb0c211324d1449f4594ece22656b3eb04bfd92116d30a1759dcbf485d25711c93e049fce82cbdb81efd0108aca801537df48474a297f6311600b4d5d01549df165bc0b3274fced85978af85c8dac5f5aeec71991236851003ecf62ba2e0f420624b86fae675a61f9a94abfddedd68db1cf7dda30cbb60005dedaf64e7997af3e6e00b6b840a8060a4f4d6405319094ea6c017e45ad915fdd48c952441ae06e1f22762c6df1bcb99a8aac5fd3d4bd15081a6063c78bc10387629899f79f1ef00e90876fe61485d2b106398cec1590a5a112ad0396fd42402a80059fccee25561f96a79889c30ad2087bff13188d77ebad2673ecde2054a5bbef763ea9c2418d1818d26ff0bebd9a483fd741a523160d8feedd41d6c0ac90815f11561e04c5fd71de8566a8858ae907bd1b3c7db4d3e61d06ecdab35174d6898cef135cda0146149b94ee4bd45314e24e11d3e207588b92c2e91fd1cec11dd124b73d736aaf7c81e10d8147c075fb66dc944d3c738faef28686d06e3e934eaf002e6f3719c1eb2c554de436961b1253ed9f04278dc5398e99a03350963746452314d5fd5bb72752aea457fe77ad2e3c4dbf58e9cc2f70bb9d81c3199585250c45439a4f20e0004bd2a2749e932f9ceb3454be2ec9e59e8731e31cd6236e2dba0e56bde1cce9187e60e685f2ac8bc15804e169cb594aca03098f96a8d17e8a2d7db9275677555619234ece51c9c71ab7e9f05ead46ab6baf1415099659def3d7da15933590ce56135372a7ea173d97e24fc41fc854da5cbdcfec3a955a2f7264e11c92b5e164892d59311b680b44e895bea1d666d0d2
Brunner,a21118815834c565eb17d095,46dc4a82c159f9031169b7401238aacf,86f27961fff1084639ce83c3f6e15f8f,a54809826d06452bb00e9af7a3d109936c1b2a32654de05b4e7a202b3785c848f702d4d5488f699f3a4716d9e60e1d27100892285707644156ae7a4f79f6b32b5691f6d47fe41015d1d5c262ca9bbaccc6b82cd70640bc0c88fc1facf864d7f1cd3238e2ac02dfb6c8ab6bb241fc9e
Claude,75399a35eff07a7f92a551d8,f3fd015d5459142a607b1c8d72a44036,acbb07a0b05ff5a1ffad512d6671b08a,5f244e98783bbd11c918fd29ad055dd724ab0f8370a8f57230113fcf642a797fe282a2eb5ac37d676f04f7bacfc01f30c79f85045cc963f33110c723aaa8f6a52200483cdcbc4268b16c9e8528146fcb2934a2fff91533628feaccc61f8b31f89ddcc72edf0f5386d3c6a0a8ed7964a6b975dde569abb34105aa0f9cca0bada3128cafc23a6194bc7ee14e40052627f24a9086cf36fe3d014edfbfccbcc08ce0d03aa337421d857215166e9ffa97f4be44674d4027786c37b4236a03f3895d42f0dfa720681e3fd47ad49dba218073201aa103090f7507318008f1ea2dbeb681a0203ed1640e57528e97a7cf0b08251bf308f11809ea733a7695f69efcedd00173d0fba80abb91f24279cf529a7570f754e633e0749483ea2dfeaf7da62b919729d6bcbb5cf40f3b42266f0eec69dd151ea6b921c1e1494feec72aeac3327fda6ae96e023c63b8f6c4bd41fd756511dc6b44caf9fb8ffdc9e54292bb5f163cccf6a1e17fbb3f3796c39163e914f0f39253e6884d0917adb788453147f0f622850a2dac6d725ba6e256be1df35e7ea8796ae969050f81289355d5577b3463bf3a3fb3ad6dba91d6d3a9ec2b27e5a6c2aa068f7cc95a81f38164c04622e85f8fb9c20e60e9d0f55053e6d2fd41032a6e43be00b927e8306643f9b88ccbeb22f4d5c1a30047676f47d99705d5fff4f1241ebdb25f258c79e5dd72a9d312865d05e5414137ab8ad0454cba790a769063ea69f7ce6aadbc0e3ddd8926f43d7f43e44d3904b262e20bfcebdefd51d0e94d65154fa1c0c77ba1283b0447efd7d52ca16dc166308a5d20fa26ccf66317967c248965de7f041f790faead3819b92d085cc6f0174b0770da4321abd9978a34dcf44284eee6f6146478c1f1d73a5da8b75530221b9c23390ccba52b9a738ce0c6832a30f44f417fcf3c7eed26e913b68bde7752b207fa4e640844de613436aa6fa7ad9ee751b5a6d4b9603eb701b579280212a5a5e2c0922fdfc9505ce33b394949f325a784ce53d2fe279792a1776dd53ee71fe1100e5af2833cb4b6b955c066ce3858e79f0cf4d74e71c5776700aa655d6bf10f1352d4aeb5d980ec4bdf2858394d19a96dbaf8fcf0ae6b68bc5faf4cf7bff28b4913ed06873f6ff49f3e1e1f662e8b5a1dacfa71e2d611e58c7bfca909ec796f4bbf0ae0aef2d563bb40956ba9a8f472c1f3b74e04311e913f8477257433f9a66e1a36ade093c2b6c154c9053df0dbc85febdc4a6aa60d27f419ca0b549ce9b18ed06d951bca3b2970891f4b84a56de1587cdffdd038f8fd987f546993b1e32dd5f45d4e654e9df11adddfce3d56ab68c5945724205af2d6e12d9930b40dc4d52bbf1c8f2a2f4c52987f21f20db665a405058f00cc792d4aa732dd125cc8240981619deed7bbde8beab8a3a408c10fe60cda86988ee0375ea142a93c96fec01e0d1af08cc59b534524fa288b0f18d09f82ef4d21e4a9f2797c30954c9d453e4d0156784d95f61d27ac1cff11a267761525d1e5a39d145059c236852f7ed3328d8e7827f07b1113b9192d77f25a08a8ab185afa0a1d8573363b863abc3fe89c6407ca2811521cfc02570e259839aa54011523fda6fd0399fdfe7c1bd2b3f0105f86397478a0b6c46033cc26c9351db45de19343ea248604da9f170184fc039b1dae58b6a5206ffd2fd84eefaf9eda8c147c30561e63bcc3e5b06f83fa33d95972726d174be364f95ce639597018d36f2cd3413ed858cbc75248f3408dd1faa8cd902ac4d2fa4d9ab41b8bf24091a22018ce0f3dc32c9393947496c79257c3032a67a3e076f69ba79de2a960347fe71536637cda78bb0e8eaf9de40e01b9935989d01a5932358cee2b6e8c7777d0a542c46502f1b146b1a26691ee47389ffd56349c2e77a79b4912ca40041764a1fb53564f62f8bd354b4c5481ddcfcedbd3dcaa55f8f525cde81174a80608a9d190f0d9c5c05e7aac0e56301350c95d3edadff8eff9b4a8c1cf6d3f6ddcefe6bba24ec66048b705cf8d9e2201418875eaa72ddb057ddc8c14174981bac51647bfca9c47ea0975644618c0231af16da75b6504ca2901622993cc8744a0d440e2c910cd055c426d73bf3c1b83fed63c2c455a6d8a2e36c8075838b851c37a97d45e2c33377276055b3a72a0e4eda484ae537ce59e077f34a4df2846727351757cfa94c8c8644a0bd40ac5b2a37ea50de68bfd9e815032a2c1c4c59e53d11a1bf229b8e40da30034647b70d8b717fb417b19c23baa243b9c732351df7cecc5b626ef75a6e6b215559310b766211fcafd4fc7c803306a72727166eaff2bdce53038905c30ec7072a471eb0a2bb6e70d59146c1f051e00d75214b7ebb9bce7f26db6e3f4605da7885f4068d770a07f1b94434d68e5c4b829cd730702b634bd605ed24b9837771da33adf583e8199115756794663ab29f16293341fdef3bdfa6928aec8496adcf9ab2cfc648fd95755519d713f12e53f3c658de833413fdca621eefb3503363d2925c6097066f669d08cf6c1fcd8f7f1634411d901122ded5e3a381029aa2a644d021dc481a2ebd4eab99fbd6a9606932488a88d82ff2644ddeb76ad54724645ff450c3bb4095e2d879e6e08475a6ee76e04bdd9998e8cd920584f1268be4210ecd05463637850f10234139fc8ccee783bf06392839da6cf61da44d18fe09c91527fa019fd0bb784a2aea7b32098aa5547c8d4830e2f4b41331b5ca8303886ed28f951f512484f2ff9ea5406785a76d9feb175df675560503bf6b9e884d68184260c01445e78ed053cc39cb33919aca3abac725fb44fedaeab4d6748e850ba90dafb8b724ad8875e6e67a34442f422aab86350ba7111bd7a52541a947656aa0ac60cb6871ec51ebf07a12e137b7dc17e0ecebf4480774142135784b470eca99637daa5703675f9fb69ff0831ffc01a47c26c87f157431d823a145f4f1abb49cb5e277402395f5fb6ad67c4e9076974e88a15042285b52583f1907359d611841ba1496ec8502f63458802c9acbc81986087e01d827faaf7f782fd85543d7d262985528e40976a068a8597fc152c06da2c027587e642ab04ec9eedec58a73d31ee929bdd0d0b79018ebc1eabcbdc0f289d4448df33832ba8cd137e11b99e609474a5f2466525a50fe1aebfec09eead7a11b4c12c316ced1d0994e358450e0b617cce46d6da70924b51203ca17b8a8ef5f2e6a79c7cb1d2382b3a1e08ee2cfa90399b8da9f90475851ede6b4f43c4797464d4dba3a1964fc7a79a238293f343cde358c6df301a14b79b2d188011c658e9f6aba20f5ab466b111ba86d63cefca51380d624bc9eb1c0a90cae3d44257007cd56d1ffb6530f9d823ec12ad487bf9019f0a5e919171062c8d1e4ac3e8c25ad8c42ce921a228175bace8fe94d71abba8d6d3d0fc6eeba66982fe8b455f75febc0110541fea932869721b2fd934b85524f71dd15797da61978a727c15f8ffda58784a8de41fa6e3fc228a24a94ff7d2986ca1bf1214fdf750cc8c15c6d6574e748fdd4d44f7f151f1f543b66b3d2237
Dud,bd1251d5608613fc58dd0706,a1df29bca0ae24fe6d656e1f55506ffd,3f559ae3727f4205d0d85736a9c4f08e,d2687c3ddc27a27c36ab700f59cd39d96796e4
Emul,0d571d6257db19acfece13a9,e9dae11fe3da2593fd06b52e51a7c352,9b3caa6f26ce42607bd70ffa92e89a6d,dd9e14da65c894616bd6b481add526a92605e1
Frank,aa70d936155b272c869bac90,ecf6ec68a70f4ce7a9556b580793f3f2,3a2c896c5469affa0f2fdc1e7cc2c01b,d405db80787f729391945e255a8907542055e6f718e24404193f95e13dd838d553cf2acaf483a525aa5ca18a31c01f248e9364ee5ce07dda97c6203f87b670042ee0c6dc1ecfea24f007e31366ba9cbad1876c308855eb6dd133e25e9d2d9848f1971706e62ed23869b223d330ff02543327cb391009fa29af8500fd2a9316a97402ec7be79fe47f80fd45309b2ec8d0f5c3fdcc6acdf4523a60e62296ea051bf61e3c1a6182ec3be3259fa0a17dab1333e4fa4cf8df5c667f257c6ac8f94b333ecb4f73f0d254f5993780f8441f453ec22145bfc12d4430f8589eba2128c2d8d28d1e141ee156df9b316aaac5a3883b352cad297ef2d77f670511783087da7fb38d205458c781eb6d5b6a8c5586f04ee46757a1def26e7972483bde1789ab45de65b01326d8aadc19855d633ffff252191dd8e69ad64230fe35d7ad76d2cc79e33dd8a5cecddc6026a3dc3fca48429eb545e5f8cd137c4ae642a7f3a218203ae0325d588d14f384e730f54b3e12b8f1da33a688ce90e65dc16eb1e8fd70cd2322e5628d735acfc186156a387ae268e05db567fc9d2bc168a4725598f411141b79b1a7e8de3009df86317c10d353208f6e5b9b75923a52ad63bfff43d5b4cb02816dbd8ef5291737af28dca3c45a82a9d03c7a32a16e85529883ced99265557ebeeec5a53e02e2fd29b763d4c72fb8adff990418163cb0f075e5d9fe28bd868a15ca45d0ed3a24a0190c3cd981a200f5742dd6c4318693851b7428c2f6452aebf8241429e050ec0c237ade66cad383c896f815c6a689e94b51da00c3d0025a682d85f4b5b7ad4437bd534b4a76d8734a12124de6f66e621a17cac292e1d4f89d9be9ba2e76607418821779439bc05bd6bb055d54a52b279d7ffac79a9c09d575011d1ec172c9163541a3efcf34a5770fa8afb2bd00f6ef7197c9b63f8907d6ddf8420e57ac2e9434f5cffbbc977f41c998faa991b28e0154908f29ca56e2224123d7db4e998e8a47af10adae4a0228b4ca2601484cb6e1c166e83368fb059a0355f44922866701cd9aca697c82000a4df35dbe72cdd3a6afbcf9d3e8ad8db0b4e38b210085f402a5159431a2ad75b73981b687d11c18abdad60414e8052c2fe75fe6425804b10df37852b553349836ad2e3a00d360ec052236e732e1d3124de98553b1c8462bbcad11d6cc3af984c5c2f7d70d8e999c0ae92381f21edddd6c00a0f18d652ad2ad250d561d445094c574a83b0f9d7ef62883eac9fa22b3f0d9ee1d0dbc3d3cc23dbec58c13bef9c7e1c4259ceaba0eb14f4e83de1d31c59cb534154d6833ea4e75362782bd3b63bd95d514c668a0971573a2b27d4b3522d951ab8a2b46a420562df3b57925259a72a108d64357e6d4919b21b6e8ea5f54367a4847f26b9ccfa7
# passwords.csv
username,password_hash
Alice,2db81a83a2038e68795e78e1fffaf2d7270ae18fe2ae99bfe6a8beb5823b8df9
Brunner,5e2b36351799d86e074d9c3344b789da448f3de1f0c5218332f1eaa9bfc083e9
Claude,7e9eb12eae8c949bfef7aca8c4a9858c5555811aa80d212211d10b9b714b047c
Dud,9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c
Emul,9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c
Frank,d1af4f9df518a7f1ad8bfd4a3e92acc1fc4725af7e976d49023f90a42f608ea5A
# nut_secure.py
#----------------------------------------------------------------------------------------------#
# This module is part of the peppernut app (secure user store of and access to their recipes). #
# - Provides the app's security related functions used for all password related functionality. #
#----------------------------------------------------------------------------------------------#


# Security Engineer Notes:
#-------------------------

# So the boss said I have to document my code better. - How I asked?
# "Don't just write your code when you're coding: Write your thoughts and reflections too!" was the response.
# So what am I thinking? Like:
# "... Why hasn't anybody thought about how difficult working conditions are, when the project you are working on constantly 
# reminds you of peppernuts, and you don't have any? :-( 
# (... wait and by the way hope that won't affect the security of my coding :-o ...)"
# Ah wait, today's coding tasks of course. And yeah maybe it could be beneficial for maintanability with explicit code considerations 
# along the way? So here goes I guess:

# Fundamentally users log in with their passwords, so only the right users can access their respective recipes.
# - But more is needed for security in a state of the art environment. Thus here some specific further security features:


# 1. Password hashing: Storing passwords securely using hashes.

# To be able to verify the users' passwords, rather than plaintext passwords their hashes are stored: The result of 
# a one-way hash function applied to the password, whereby original password cannot be easily retrieved from the hash,
# even if someone by one mean or another gets access to the hashes in the database.


# 1.1. Pepper

# A so-called pepper is used, which is a secret value added to the password before hashing, to prevent certain attacks from someone 
# who gets access to the hashes: A measure against guessing or bruteforcing until finding the password that matches the hash.
def create_pepper():
    """Create a secure pepper for password hashing."""
    import secrets
    # A size of at least 16 bytes is recommended, so an attacker who knows one of the user's passwords (e.g. their own account), 
    # can't learn the value of the pepper by bruteforcing its possible values, until finding the one that added to the known password 
    # gives the same hash as the stored one:
    pepper_size = 16
    # ... wow that feeling again, I could really use some peppernuts... were was I, something about creating the pepper?
    pepper = hex(secrets.randbits(pepper_size))[2:]
    
    # The pepper is then securily kept seperately, rendering the hashes alone insufficient for an attacker.
    with open('secret.txt', 'w') as f: 
        f.write(pepper)

# And this same secret pepper is then in turn used to verify the password:
def get_pepper() -> str:
    with open('secret.txt', 'r') as f: 
        pepper = f.read()
    return pepper


# 1.2. Different hash functions

# There are different state of the art hash functions for different use cases, each with their own properties, strengths, and weaknesses:
def password_hash(password) -> str:
    """Create a state-of-the-art password hash, secure against different attack vectors."""
    # So we take the pepper and use it together with hashing functions to attain the security described above. 
    pepper = get_pepper()
    
    # Now SHA-256 is a relatively fast hash function, which in principle can get us quite far with regards to many of the purposes:
    from hashlib import sha256
    hash = sha256
    ph = hash((pepper + ":" + password).encode()).hexdigest()

    # ... wow, again, I could really use some peppernuts... where was I, writing something?... ehh, what to write, maybe about testing 
    # the functionality so far(?) - like how the test with password="Test123!" at this point had the value of ph be:
    # '8b56d663500f6f36f7b2f329cbcfe65851b146df3567e0b2fcf896391d641b7f'...
    # Ah yes - and so as intended this documented the non-reversible transformation of the password with the pepper giving 
    # a different hash value than if the password had been hashed on its own.

    # With this initial- or pre-hash as I might call it (as for why I used "ph") warding against potential denial of service attacks 
    # when very large passwords are allowed (as they are meant to be in this app), an essential next is the further application of
    # a relatively cumbersome hashing algorithm e.g. high in resource demands to make it harder for an attacker to bruteforce the hashes. 
    # - And thus the state-of-the-art recommendation for the purpose:
    from argon2 import PasswordHasher 

    # With this we now move beyond mere "secure-hash-with-a-pepper", to security complete with the algorithm's superb weigh-in requiring: 
    # - Processor time: Potentially from 10.000 to 1.000.000 times more than SHA-256 ! 
    # - Memory: Rather than the few hundred bytes used by SHA-256, this monster can with typical setups take from 64-512MB - per hash! x-D
    # Try to bruteforce my app's password security now I dare you! :-D And not only that! Also...
    # ... wait was that the doorbell?! Was there supposed to come a peppernut delivery that I am unaware of? I'll be right back...
    # ... arh what that was just the regular postman with a bakery sales brochure of all things. x-(  
    # ... where was I... something about ph?... what was ph? Theee... password hash? Ah right, the return value I guess:
    return ph 
    # Anyway, I have(!) to get hold of some peppernuts one way or another... I'll follow up on this work later...



# 2. Password validation: Ensuring that users choose strong passwords.

# ...wow from what that employee on the phone told me, there might actually be a peppernut delivery swinging by here soon. :)

# So, on to the next security feature: The password security measure is of course only as strong as the passwords chosen:
# - If the passwords are weak, they could potentially be bruteforced and or guessed by attackers.
# - I will therefore implement validation to ensure that the passwords are strong enough.
def validate_password(password: str) -> str | None:
    """Validate password strength, returning the password if it is safe enough, 
    or throwing an informative error if it is not."""
    
    # A strong password should meet a number of minimum requirements to pass validation:
    descriptions = {
        1: "Password is longer than 7 characters.",
        2: "Password contains at least one digit.",
        3: "Password contains at least one uppercase letter.",
        4: "Password contains at least one special character."
    }

    # We can use the variable `check` to keep track of how far the password gets through the checks.
    checks = []
    if len(password) >= 8:
        checks += [1]
    if any(char.isdigit() for char in password):
        checks += [2]
    if any(char.isupper() for char in password):
        checks += [3]
    if any(not char.isalnum() for char in password):
        checks += [4]
    
    # Either is returned a valid password passing all checks, or if not, the user is informed about the specific requirement not met.
    # ... wait where is that peppernut delivery when you need it... I'll have to finish this and try to figure out: 
    if len(checks) > 0:
        raise ValueError(f"Invalid password! Your password failed the requirements, specifically due to the following checks:\n"
                         f"{"\n".join([descriptions[check] for check in checks])}\n"
                         f"Please try again with a password that meets all the requirements.")
    else: 
        return password

# ... 'kay I'm back, but met the boss on the way saying that now my module is the last remaining before the app is finished, and 
# thus everything else is already waiting in the CI/CD pipelines, with the entire app ready to autodeploy as soon as my code has 
# been finalized along with the others... :-| ... I of course tried explaining that of all things, you do not wan't to rush security...
# ... but on the positive side, I finally got hold of some peppernuts! :-D So back on track :) - the first handful already having 
# their effect, so fit for an additional bit of secure coding. And with some more peppernuts still waiting, so lets wrap this up.



# 3. Data encryption: Encrypting the users' recipes to protect them from unauthorized access.

# As a final measure against attackers, who one way or another gets access to the stored data bypassing the login system, 
# the recipes are extensively secured by encrypting them with a key based on the user password: 
# This way, no matter who gets or how they get access to the data, they won't be able to read it without the user password.
def encrypt_data(data: str, nonce: str, password: str, hash_salt: str, key_salt: str, decrypt: bool = False) -> str:
    """Safely encrypt/decrypt data with a key based on the user password, 
    whereby only the user is able to read the data."""
    # With the use of non-secret values, salts prevent against lookup tables, and a nonce changed with each encryption ensures
    # that a later encryption of the same recipe yields a different result than before. - Thus attackers (already prevented
    # from reading the recipes) also cannot even deduce whether the decrypted content changed or not between two encryptions.

    # The gold standard for encrypting sensitive user data in modern systems is AES-256-GCM, recommended by NIST, OWASP, and 
    # numerous cryptographic standards as the default AEAD (Authenticated Encryption with Associated Data) mode:
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    
    # Again as earlier, to effectively encumber bruteforcing, the state-of-the-art heavy hash function goto is argon2, though for 
    # deterministic key derivation (like here in encryption schemes) where we need to reproduce exactly the same key 
    # from the same password + salt combo, we need to switch to the low level API (hash_secret_raw), to explicitly control the salt.
    from argon2.low_level import hash_secret_raw, Type

    # This encryption related hash is done seperately from the password hashes we already stored, since otherwise someone with access 
    # to the password hashes wouldn't even need to discern the passwords at all to decrypt. 
    argon2_hash = hash_secret_raw(
        secret=password.encode(),
        salt=bytes.fromhex(hash_salt),
        time_cost=5,
        memory_cost=262144,
        parallelism=4,
        hash_len=64,
        type=Type.ID
    )

    # To cleanly distill our argon2 hash into the fixed length, high entropy cryptographic key for our AESGCM encryption, 
    # the industry standard here is HKDF-SHA256:
    from cryptography.hazmat.primitives.kdf.hkdf import HKDF
    # (Here importing SHA256 from another library - still effectively the same algorithm, but just a specific format for HKDF:) 
    from cryptography.hazmat.primitives import hashes 
    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        # Though using the same hash_salt as above isn't usually considered insecure in simple single user setups, using seperate salts 
        # even here is the gold standard recommendation, especially in systems with long term security and audit requirements:
        salt= bytes.fromhex(key_salt),
        info=b'user-data-encryption'
    )
    key = hkdf.derive(argon2_hash)

    # Thus all prepared for encryption/decryption (two sides of the same scheme for symmetric encryption like this state-of-the-art AES):
    aesgcm = AESGCM(key)
    if decrypt: 
        return aesgcm.decrypt(bytes.fromhex(nonce), bytes.fromhex(data), None).hex()
    else:
        return aesgcm.encrypt(bytes.fromhex(nonce), bytes.fromhex(data), None).hex()

# Alright - I just thouroughly reviewed my work here in section 3, and it's rock solid :-D so ready to wrap things up with section 4.



# 4. Reviews and tests of the code

# So with that powerhouse of a section 3 above, nobody will read these peppernut recipes without the corresponding password. :)
# - And that only the intended user knows or is able to figure out the password, is already ensured by my work in sections 1 and 2, 
# which I also already thouroughly revie... 
# ... wait did I review them yet?... I should probably double check, especially also since all the other code reviewers and testers 
# are presently all away, taking flex time off, trying to find some peppernuts, so I should probably... 
# ... wait, what's that scent in the air? - So strongly reminds me of peppernuts for some reason. But that's right it is - the remaining 
# peppernuts - I had forgotten all about them! :-D Was I done here? - I probably was, and if not I'm sure one of the others will test 
# and review and let me know. - So commit-push, there we go, now time for peppernuuuuuuuuuuts!... 

Reading the code, we found that the salt has only 16 bits, eligible for attack. And the comments provided a pair of known message and hash:

sha256(salt + ":Test123!") == "8b56d663500f6f36f7b2f329cbcfe65851b146df3567e0b2fcf896391d641b7f"

Bruteforce the salt:

import secrets
from hashlib import sha256

while True:
    pepper_size = 16
    pepper = hex(secrets.randbits(pepper_size))[2:]
    hash = sha256
    password = "Test123!"
    ph = hash((pepper + ":" + password).encode()).hexdigest()
    if ph == "8b56d663500f6f36f7b2f329cbcfe65851b146df3567e0b2fcf896391d641b7f":
        print(pepper)
        break

The salt is e9d8. Then, we have a list of hashed password:

username,password_hash
Alice,2db81a83a2038e68795e78e1fffaf2d7270ae18fe2ae99bfe6a8beb5823b8df9
Brunner,5e2b36351799d86e074d9c3344b789da448f3de1f0c5218332f1eaa9bfc083e9
Claude,7e9eb12eae8c949bfef7aca8c4a9858c5555811aa80d212211d10b9b714b047c
Dud,9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c
Emul,9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c
Frank,d1af4f9df518a7f1ad8bfd4a3e92acc1fc4725af7e976d49023f90a42f608ea5A

Let hashcat to do the dirty work. Create a test.hash file:

2db81a83a2038e68795e78e1fffaf2d7270ae18fe2ae99bfe6a8beb5823b8df9:e9d8:
5e2b36351799d86e074d9c3344b789da448f3de1f0c5218332f1eaa9bfc083e9:e9d8:
7e9eb12eae8c949bfef7aca8c4a9858c5555811aa80d212211d10b9b714b047c:e9d8:
9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c:e9d8:
d1af4f9df518a7f1ad8bfd4a3e92acc1fc4725af7e976d49023f90a42f608ea5:e9d8:

Each line is in format hash:salt, where salt is e9d8:. The salt of hashcat does not add colon by itself, so the colon is part of the salt.

Then, let hashcat do the job:

$ hashcat -O -a 0 -m 1420 test.hash ~/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
# -O: optimized
# -a 0: wordlist attack
# -m 1420: hash is in sha256($salt.$pass) format
# test.hash: path to file with hashes
# ~/rockyou.txt: wordlist
# -r /usr/share/hashcat/rules/best64.rule: rule-based attack

It gives us four passwords:

$ hashcat --show -m 1420 test.hash
2db81a83a2038e68795e78e1fffaf2d7270ae18fe2ae99bfe6a8beb5823b8df9:e9d8::gfedcba
5e2b36351799d86e074d9c3344b789da448f3de1f0c5218332f1eaa9bfc083e9:e9d8::abcake
9d1a04b41fba1a699c291df1bad30913d78293c7224439535c88459f4ffcbf9c:e9d8::dud
d1af4f9df518a7f1ad8bfd4a3e92acc1fc4725af7e976d49023f90a42f608ea5:e9d8::letmein

Then we can decrypt the data for the four users:

def encrypt_data(
    data: str,
    nonce: str,
    password: str,
    hash_salt: str,
    key_salt: str,
    decrypt: bool = False,
) -> str:
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    from argon2.low_level import hash_secret_raw, Type

    argon2_hash = hash_secret_raw(
        secret=password.encode(),
        salt=bytes.fromhex(hash_salt),
        time_cost=5,
        memory_cost=262144,
        parallelism=4,
        hash_len=64,
        type=Type.ID,
    )

    from cryptography.hazmat.primitives.kdf.hkdf import HKDF
    from cryptography.hazmat.primitives import hashes

    hkdf = HKDF(
        algorithm=hashes.SHA256(),
        length=32,
        salt=bytes.fromhex(key_salt),
        info=b"user-data-encryption",
    )
    key = hkdf.derive(argon2_hash)

    aesgcm = AESGCM(key)
    if decrypt:
        return aesgcm.decrypt(bytes.fromhex(nonce), bytes.fromhex(data), None).hex()
    else:
        return aesgcm.encrypt(bytes.fromhex(nonce), bytes.fromhex(data), None).hex()


print(
    "Alice",
    bytes.fromhex(
        encrypt_data(
            "c9bcc3e039708bbc7edfff47782f4f3737addf9896bfb8b63d1bd03434060efc519ad260bfea942dacf90468654548bb7fd1a3b0095f6c11ba15186beafe4a1872eb85a6340438e987bf0e8cb9713486ec9478d8c51e66e185f0ca757754505d902129e5a2067870fb8120e71683cbb51929ae823d98dc24a4e0f752bfb6351390b90d4b6e79b2f135976d4a1035607ed2d8ecf5e7983eb89b0de18b7e19e4cfc0ea0f2291296a5b947ece929c942db684426559c0c1b7f570704a1822c0b0c0eddb93ddf459d3c401d61e3947837a954017c097897a13f9b106874e01e0dff580e0902b644b6e8639ba4062158e9c59ea1860f9a7f5bfe3e55225747edf944bb772b39eefae3320ce040987e63738e1db55610274c020a33af00a4e05372cec7b8b011114c77ee80f80279ffeb026cd53add81bd8d89a4657bad49b2fa5bf31aea991729b880c457da0e2ec359550a1e57a9079e390b406c79abcf3db7e6bd14f94341e53628476e29bc791c1e94137f702f8e6f1c547838fc3f9b6e055544f687038318054891d3f0c05b56e885b29557006019433506d2244f0a2754fc30baff0f2a9f695e67fae614edc6e752c097617180f450604b8210bfbd2dc10ba0ea61853e0dbc5fc4c73d0fb5fc1a548ec37810d0780c70537dfafd0f46dda870c7c2bd26c639b516083fc8a0602afc2ea013884bc7073c12ccf8ec51d435aee9aed0669565d97ada1d6797e90a41106824f85d1ab76cd1312ea157511f76d69e898dc3133df3380d0978e5fbbfe669951db1c100c84c34b7df409708b2d379f312f5f8c45c090b37ff6d540a49c67d0871b1fff17e31c754529c85982fc7d153620365c5bb6526b868b3215d8d15b0678fff305a24b209f0e4fba6e671f81485c00d41df0c33f8182189f6e9b7387dc522b5a5c045f5caf51c8105c46e699cc597d4583e9d1d1a187ac890c03b95c96d950823614fd0a941737a8d5c8757fc8b9ffe83284de736cc1098b6e6ed958c7d13ceba9c1f767a09acbf48f0fda89f88ae9a9356a08764f852ceec65325b44df4925e2fb0ca8a1afa69e84d6ce1c072eefdd1c30dc6c2428acac0ee7f846523ecafdc55a2cd31fac5952c383a5bbb7a86c75f4d5dcf39513507106c5052688b7b408019fe9f5021058fdd41f35e051e13dcbc050676fa538503b61082c74df815111e913ea9bbbea1625b2a5a6e2a240973dbe301da8380f81b1031dc588fc81a5f8a5d79a60afdf1b7976769fdfbfd801078f4558627433d48d67237cc0ca4ddbb0009b35e2e970383aaeeaaf7d4f14f58e971e00e968b5e50df36deb9fc4ca8401bca1451646a5a21128d1c0b330569b4edf1fe1cd25553b5e68b51f8c9897e1ab6c36517b3467364a03d8cbe1eeb5fb4d8f01c4cd20ed3a754953bebd9fe8a937b0260fd35198fb1980a61dba7d883877b297a7879bde77ee7e66201d262b03b6f7477113ab2ab1a88f8d8a0266dc01f29c5cecd7d08e5c32d6a8f0720e62ff8aa201b2cdde949abb0c211324d1449f4594ece22656b3eb04bfd92116d30a1759dcbf485d25711c93e049fce82cbdb81efd0108aca801537df48474a297f6311600b4d5d01549df165bc0b3274fced85978af85c8dac5f5aeec71991236851003ecf62ba2e0f420624b86fae675a61f9a94abfddedd68db1cf7dda30cbb60005dedaf64e7997af3e6e00b6b840a8060a4f4d6405319094ea6c017e45ad915fdd48c952441ae06e1f22762c6df1bcb99a8aac5fd3d4bd15081a6063c78bc10387629899f79f1ef00e90876fe61485d2b106398cec1590a5a112ad0396fd42402a80059fccee25561f96a79889c30ad2087bff13188d77ebad2673ecde2054a5bbef763ea9c2418d1818d26ff0bebd9a483fd741a523160d8feedd41d6c0ac90815f11561e04c5fd71de8566a8858ae907bd1b3c7db4d3e61d06ecdab35174d6898cef135cda0146149b94ee4bd45314e24e11d3e207588b92c2e91fd1cec11dd124b73d736aaf7c81e10d8147c075fb66dc944d3c738faef28686d06e3e934eaf002e6f3719c1eb2c554de436961b1253ed9f04278dc5398e99a03350963746452314d5fd5bb72752aea457fe77ad2e3c4dbf58e9cc2f70bb9d81c3199585250c45439a4f20e0004bd2a2749e932f9ceb3454be2ec9e59e8731e31cd6236e2dba0e56bde1cce9187e60e685f2ac8bc15804e169cb594aca03098f96a8d17e8a2d7db9275677555619234ece51c9c71ab7e9f05ead46ab6baf1415099659def3d7da15933590ce56135372a7ea173d97e24fc41fc854da5cbdcfec3a955a2f7264e11c92b5e164892d59311b680b44e895bea1d666d0d2",
            "394acaf0e2550e2dc5bfacac",
            "gfedcba",
            "a85fa1f0edbb9afd0ac90f6439f52d88",
            "2c8746070bd2ae310727efc0798d92ca",
            True,
        )
    ).decode(),
)

print(
    "Frank",
    bytes.fromhex(
        encrypt_data(
            "d405db80787f729391945e255a8907542055e6f718e24404193f95e13dd838d553cf2acaf483a525aa5ca18a31c01f248e9364ee5ce07dda97c6203f87b670042ee0c6dc1ecfea24f007e31366ba9cbad1876c308855eb6dd133e25e9d2d9848f1971706e62ed23869b223d330ff02543327cb391009fa29af8500fd2a9316a97402ec7be79fe47f80fd45309b2ec8d0f5c3fdcc6acdf4523a60e62296ea051bf61e3c1a6182ec3be3259fa0a17dab1333e4fa4cf8df5c667f257c6ac8f94b333ecb4f73f0d254f5993780f8441f453ec22145bfc12d4430f8589eba2128c2d8d28d1e141ee156df9b316aaac5a3883b352cad297ef2d77f670511783087da7fb38d205458c781eb6d5b6a8c5586f04ee46757a1def26e7972483bde1789ab45de65b01326d8aadc19855d633ffff252191dd8e69ad64230fe35d7ad76d2cc79e33dd8a5cecddc6026a3dc3fca48429eb545e5f8cd137c4ae642a7f3a218203ae0325d588d14f384e730f54b3e12b8f1da33a688ce90e65dc16eb1e8fd70cd2322e5628d735acfc186156a387ae268e05db567fc9d2bc168a4725598f411141b79b1a7e8de3009df86317c10d353208f6e5b9b75923a52ad63bfff43d5b4cb02816dbd8ef5291737af28dca3c45a82a9d03c7a32a16e85529883ced99265557ebeeec5a53e02e2fd29b763d4c72fb8adff990418163cb0f075e5d9fe28bd868a15ca45d0ed3a24a0190c3cd981a200f5742dd6c4318693851b7428c2f6452aebf8241429e050ec0c237ade66cad383c896f815c6a689e94b51da00c3d0025a682d85f4b5b7ad4437bd534b4a76d8734a12124de6f66e621a17cac292e1d4f89d9be9ba2e76607418821779439bc05bd6bb055d54a52b279d7ffac79a9c09d575011d1ec172c9163541a3efcf34a5770fa8afb2bd00f6ef7197c9b63f8907d6ddf8420e57ac2e9434f5cffbbc977f41c998faa991b28e0154908f29ca56e2224123d7db4e998e8a47af10adae4a0228b4ca2601484cb6e1c166e83368fb059a0355f44922866701cd9aca697c82000a4df35dbe72cdd3a6afbcf9d3e8ad8db0b4e38b210085f402a5159431a2ad75b73981b687d11c18abdad60414e8052c2fe75fe6425804b10df37852b553349836ad2e3a00d360ec052236e732e1d3124de98553b1c8462bbcad11d6cc3af984c5c2f7d70d8e999c0ae92381f21edddd6c00a0f18d652ad2ad250d561d445094c574a83b0f9d7ef62883eac9fa22b3f0d9ee1d0dbc3d3cc23dbec58c13bef9c7e1c4259ceaba0eb14f4e83de1d31c59cb534154d6833ea4e75362782bd3b63bd95d514c668a0971573a2b27d4b3522d951ab8a2b46a420562df3b57925259a72a108d64357e6d4919b21b6e8ea5f54367a4847f26b9ccfa7",
            "aa70d936155b272c869bac90",
            "letmein",
            "ecf6ec68a70f4ce7a9556b580793f3f2",
            "3a2c896c5469affa0f2fdc1e7cc2c01b",
            True,
        )
    ).decode(),
)

print(
    "Dud",
    bytes.fromhex(
        encrypt_data(
            "d2687c3ddc27a27c36ab700f59cd39d96796e4",
            "bd1251d5608613fc58dd0706",
            "dud",
            "a1df29bca0ae24fe6d656e1f55506ffd",
            "3f559ae3727f4205d0d85736a9c4f08e",
            True,
        )
    ).decode(),
)

print(
    "Brunner",
    bytes.fromhex(
        encrypt_data(
            "a54809826d06452bb00e9af7a3d109936c1b2a32654de05b4e7a202b3785c848f702d4d5488f699f3a4716d9e60e1d27100892285707644156ae7a4f79f6b32b5691f6d47fe41015d1d5c262ca9bbaccc6b82cd70640bc0c88fc1facf864d7f1cd3238e2ac02dfb6c8ab6bb241fc9e",
            "a21118815834c565eb17d095",
            "abcake",
            "46dc4a82c159f9031169b7401238aacf",
            "86f27961fff1084639ce83c3f6e15f8f",
            True,
        )
    ).decode(),
)

Get flag: brunner{Maybe_we_could_mould_some_small_pieces_of_brunsviger_into_peppernut-shaped_treats?_:-D}