Updated:

1. Make the key

/give @s tripwire_hook[custom_data={key:1b},custom_name="Vault Key"]

The key:1b custom data marks the real key, so an anvil-renamed copy does not work. In 1.21.4 and older write the name as custom_name='"Vault Key"'.

2. Door and marker

Place an iron door. Stand in its lower half and summon a marker in the block below it:

/summon marker ~ ~-1 ~ {Tags:["keydoor"]}

3. Find key holders (repeating)

/tag @a remove near_key
/execute at @e[type=marker,tag=keydoor] as @a[distance=..3] if items entity @s weapon.* *[custom_data~{key:1b}] run tag @s add near_key

weapon.* checks both hands, and custom_data~{key:1b} matches items that contain key:1b.

4. Open and close (repeating)

/execute at @e[type=marker,tag=keydoor] if entity @a[tag=near_key,distance=..3] run setblock ~ ~ ~ redstone_block
/execute at @e[type=marker,tag=keydoor] unless entity @a[tag=near_key,distance=..3] run setblock ~ ~ ~ stone

A redstone block under the door opens it, and stone closes it again. Each marked door works on its own.

5. One-use keys

/execute at @e[type=marker,tag=keydoor] as @a[tag=near_key,distance=..3] run clear @s tripwire_hook[custom_data~{key:1b}] 1

Remove the closing line from step 4 and chain this after the opening line. The door stays open and one key is used up.

6. Different keys for different doors

Use custom_data={key:2b} and a keydoor2 marker tag, then copy steps 3 and 4 with those values.

7. Automatic door without a key

execute at @e[type=marker,tag=keydoor] if entity @a[distance=..3] run setblock ~ ~ ~ redstone_block
execute at @e[type=marker,tag=keydoor] unless entity @a[distance=..3] run setblock ~ ~ ~ stone

For a door that opens for anyone nearby, skip step 3 and drop tag=near_key, from step 4. Bedrock has no marker, so write the position directly and use r instead of distance: execute if entity @a[x=10,y=64,z=10,r=3] run setblock 10 63 10 redstone_block.

Troubleshooting

Check your command

The Command Error Doctor checks common syntax mistakes in your browser.