I always asked myself how to password protect GRUB entries on an encrypted notebook to lock down the boot loader and protect it from unauthorized access.

Password protect everything

Use grub-mkpasswd-pbkdf2 command to generate the password hash.

$ grub-mkpasswd-pbkdf2
Enter password:   ********
Reenter password: ********
PBKDF2 hash of your password is grub.pbkdf2.sha512.10000.800E[..].79C[..]

Define GRUB user (milosz in the following example) using generated hash and declare it as a superuser inside /etc/grub.d/40_custom configuration file.

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
# define superusers
set superusers="milosz"
#define users
password_pbkdf2 milosz grub.pbkdf2.sha512.10000.800EF[..].7977C[..]

Install the modified configuration and test it afterward.

$ sudo grup-update

Password protect everything except specific menu entries

Define users and superusers inside /etc/grub.d/40_custom configuration file.

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
# define superusers
set superusers="r00t"
#define users
password_pbkdf2 r00t grub.pbkdf2.sha512.10000.800EF[..].7977C[..]
password_pbkdf2 t00r grub.pbkdf2.sha512.10000.3A16C[..].34DF0[..]
password_pbkdf2 t01r grub.pbkdf2.sha512.10000.5D042[..].A7B33[..]

At this moment, GRUB installation will enforce r00t username and password for every action like boot, edit, and command-line.

GRUB configuration is generated automatically. The easiest way to alter it is to edit /etc/grub.d/30_os-prober or /etc/grub.d/10_linux file.
Look for menuentry definition for the wanted class like gnu-linux, osx, windows or hurd.
Then modify that line to allow unrestricted access or selected users.

I will play with Debian GNU/Linux menu entry inside /etc/grub.d/10_linux file.

else
      echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
  fi
  if [ "$quick_boot" = 1 ]; then
      echo "	recordfail" | sed "s/^/$submenu_indentation/"
  fi

To allow unrestricted boot access for Debian GNU/Linux add an --unrestricted parameter.

else
      echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' --unrestricted {" | sed "s/^/$submenu_indentation/"
  fi
  if [ "$quick_boot" = 1 ]; then
      echo "	recordfail" | sed "s/^/$submenu_indentation/"
  fi

To allow superusers and t00r users to boot Debian GNU/Linux, add --users t00r parameter. In this case, access for t01r user will be denied.

else
      echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' --users t00r {" | sed "s/^/$submenu_indentation/"
  fi
  if [ "$quick_boot" = 1 ]; then
      echo "	recordfail" | sed "s/^/$submenu_indentation/"
  fi

To allow superusers and t00r, t01r users to boot Debian GNU/Linux add an --users t00r,t01r parameter.

else
      echo "menuentry '$(echo "$os" | grub_quote)' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' --users t00r,t01r {" | sed "s/^/$submenu_indentation/"
  fi
  if [ "$quick_boot" = 1 ]; then
      echo "	recordfail" | sed "s/^/$submenu_indentation/"
  fi

To allow unrestricted boot access for MS Windows found on /dev/sda1 device add an --unrestricted parameter using an if statement inside /etc/grub.d/30_os-prober file.

found_other_os=1
      onstr="$(gettext_printf "(on %s)" "${DEVICE}")"
      cat <<EOF
menuentry '$(echo "${LONGNAME} $onstr" | grub_quote)' --class windows --class os \$menuentry_id_option 'osprober-chain-$(grub_get_device_id "${DEVICE}")' {
EOF
      save_default_entry | grub_add_tab
      prepare_grub_to_access_device ${DEVICE} | grub_add_tab
found_other_os=1
      onstr="$(gettext_printf "(on %s)" "${DEVICE}")"
      if [ ${DEVICE} = "/dev/sda1" ]; then
        cat << EOF
menuentry '$(echo "${LONGNAME} $onstr" | grub_quote)' --class windows --class os \$menuentry_id_option 'osprober-chain-$(grub_get_device_id "${DEVICE}")' --unrestricted {
EOF
      else
        cat << EOF
menuentry '$(echo "${LONGNAME} $onstr" | grub_quote)' --class windows --class os \$menuentry_id_option 'osprober-chain-$(grub_get_device_id "${DEVICE}")' {
EOF
      fi
      save_default_entry | grub_add_tab
      prepare_grub_to_access_device ${DEVICE} | grub_add_tab

Generate and install the modified configuration.

$ sudo grup-update

Use this method to change default settings for other classes and menus.

grub-mkpasswd.sh shell script

Command grub-mkpasswd-pbkdf2 works only in interactive mode, so use expect utility to automate it a bit.

#!/bin/sh
# generate hash password given as an parameter
# example: grub-mkpasswd.sh "my password"

expect=$(which expect);
mkpasswd=$(which grub-mkpasswd-pbkdf2)

expect_script(){
cat << EOF
  log_user 0
  spawn  ${mkpasswd}
  sleep 0.33
  expect  "Enter password: " {
    send "$1"
    send "\n"
  }
  sleep 0.33
  expect "Reenter password: " {
    send "$1"
    send "\n"
  }
  sleep 0.33
  expect eof {
    puts "\$expect_out(buffer)"
  }
  exit 0
EOF
}

if [ -n "$1" ]; then
  expect_script "$1" | $expect | sed -e "/^\r$/d" -e "/^$/d" -e "s/.* \(.*\)/\1/"
fi

Ending notes

For further information, read the GNU GRUB manual and inspect the mentioned shell scripts.